From 0dbe97bf8aebd168e5db21c1b87477ab07f29220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 13 Sep 2018 10:20:54 +0200 Subject: [PATCH 01/96] create branch From 1dab4c69fed5f1294972a3cd12da95fa2265ce30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 13 Sep 2018 12:20:28 +0200 Subject: [PATCH 02/96] mockup --- .../changesets/containers/ChangesetView.js | 18 + .../repos/changesets/modules/changesets.js | 0 .../changesets/modules/changesets.test.js | 0 .../src/repos/changesets/types/Changeset.js | 13 + scm-ui/src/repos/containers/RepositoryRoot.js | 7 +- scm-ui/yarn.lock | 860 +++++++----------- 6 files changed, 356 insertions(+), 542 deletions(-) create mode 100644 scm-ui/src/repos/changesets/containers/ChangesetView.js create mode 100644 scm-ui/src/repos/changesets/modules/changesets.js create mode 100644 scm-ui/src/repos/changesets/modules/changesets.test.js create mode 100644 scm-ui/src/repos/changesets/types/Changeset.js diff --git a/scm-ui/src/repos/changesets/containers/ChangesetView.js b/scm-ui/src/repos/changesets/containers/ChangesetView.js new file mode 100644 index 0000000000..a3d8effc7d --- /dev/null +++ b/scm-ui/src/repos/changesets/containers/ChangesetView.js @@ -0,0 +1,18 @@ +//@flow +import React from "react"; + +type Props = { + +} + +class ChangesetView extends React.Component { + render() { + return ( +
+Hallo! Changesets here! +
+ ); + } +} + +export default ChangesetView; diff --git a/scm-ui/src/repos/changesets/modules/changesets.js b/scm-ui/src/repos/changesets/modules/changesets.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scm-ui/src/repos/changesets/modules/changesets.test.js b/scm-ui/src/repos/changesets/modules/changesets.test.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scm-ui/src/repos/changesets/types/Changeset.js b/scm-ui/src/repos/changesets/types/Changeset.js new file mode 100644 index 0000000000..ac7b9861f7 --- /dev/null +++ b/scm-ui/src/repos/changesets/types/Changeset.js @@ -0,0 +1,13 @@ +//@flow +import type { Links } from "@scm-manager/ui-types"; + +export type Changeset = { + id: String, + author: { + mail: String, + name: String + }, + date: String, + description: String, + _links: Links +}; diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 2816cdb5c5..47815a0b45 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -8,7 +8,7 @@ import { isFetchRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import { Route } from "react-router-dom"; +import { Route, Switch } from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; import { Page, @@ -25,6 +25,7 @@ import Edit from "../containers/Edit"; import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; +import ChangesetView from "../changesets/containers/ChangesetView"; type Props = { namespace: string, @@ -101,6 +102,10 @@ class RepositoryRoot extends React.Component { path={`${url}/edit`} component={() => } /> + } + />
diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 26bfec8070..f7b0b225d9 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -2,12 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz#bd71d9b192af978df915829d39d4094456439a0c" - dependencies: - "@babel/highlight" "7.0.0-beta.51" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.0.0-beta.35": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -15,8 +9,8 @@ "@babel/highlight" "^7.0.0" "@babel/core@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.0.0.tgz#0cb0c0fd2e78a0a2bec97698f549ae9ce0b99515" + version "7.0.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.0.1.tgz#406658caed0e9686fa4feb5c2f3cefb6161c0f41" dependencies: "@babel/code-frame" "^7.0.0" "@babel/generator" "^7.0.0" @@ -33,16 +27,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.51.tgz#6c7575ffde761d07485e04baedc0392c6d9e30f6" - dependencies: - "@babel/types" "7.0.0-beta.51" - jsesc "^2.5.1" - lodash "^4.17.5" - source-map "^0.5.0" - trim-right "^1.0.1" - "@babel/generator@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0.tgz#1efd58bffa951dc846449e58ce3a1d7f02d393aa" @@ -96,14 +80,6 @@ "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" -"@babel/helper-function-name@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz#21b4874a227cf99ecafcc30a90302da5a2640561" - dependencies: - "@babel/helper-get-function-arity" "7.0.0-beta.51" - "@babel/template" "7.0.0-beta.51" - "@babel/types" "7.0.0-beta.51" - "@babel/helper-function-name@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0.tgz#a68cc8d04420ccc663dd258f9cc41b8261efa2d4" @@ -112,12 +88,6 @@ "@babel/template" "^7.0.0" "@babel/types" "^7.0.0" -"@babel/helper-get-function-arity@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz#3281b2d045af95c172ce91b20825d85ea4676411" - dependencies: - "@babel/types" "7.0.0-beta.51" - "@babel/helper-get-function-arity@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" @@ -195,12 +165,6 @@ "@babel/template" "^7.0.0" "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz#8a6c3f66c4d265352fc077484f9f6e80a51ab978" - dependencies: - "@babel/types" "7.0.0-beta.51" - "@babel/helper-split-export-declaration@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" @@ -224,14 +188,6 @@ "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" -"@babel/highlight@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.51.tgz#e8844ae25a1595ccfd42b89623b4376ca06d225d" - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^3.0.0" - "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -240,10 +196,6 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.0.0-beta.51.tgz#27cec2df409df60af58270ed8f6aa55409ea86f6" - "@babel/parser@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.0.0.tgz#697655183394facffb063437ddf52c0277698775" @@ -624,15 +576,6 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" -"@babel/template@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.51.tgz#9602a40aebcf357ae9677e2532ef5fc810f5fbff" - dependencies: - "@babel/code-frame" "7.0.0-beta.51" - "@babel/parser" "7.0.0-beta.51" - "@babel/types" "7.0.0-beta.51" - lodash "^4.17.5" - "@babel/template@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0.tgz#c2bc9870405959c89a9c814376a2ecb247838c80" @@ -641,21 +584,6 @@ "@babel/parser" "^7.0.0" "@babel/types" "^7.0.0" -"@babel/traverse@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.51.tgz#981daf2cec347a6231d3aa1d9e1803b03aaaa4a8" - dependencies: - "@babel/code-frame" "7.0.0-beta.51" - "@babel/generator" "7.0.0-beta.51" - "@babel/helper-function-name" "7.0.0-beta.51" - "@babel/helper-split-export-declaration" "7.0.0-beta.51" - "@babel/parser" "7.0.0-beta.51" - "@babel/types" "7.0.0-beta.51" - debug "^3.1.0" - globals "^11.1.0" - invariant "^2.2.0" - lodash "^4.17.5" - "@babel/traverse@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0.tgz#b1fe9b6567fdf3ab542cfad6f3b31f854d799a61" @@ -670,14 +598,6 @@ globals "^11.1.0" lodash "^4.17.10" -"@babel/types@7.0.0-beta.51": - version "7.0.0-beta.51" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.51.tgz#d802b7b543b5836c778aa691797abf00f3d97ea9" - dependencies: - esutils "^2.0.2" - lodash "^4.17.5" - to-fast-properties "^2.0.0" - "@babel/types@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0.tgz#6e191793d3c854d19c6749989e3bc55f0e962118" @@ -690,13 +610,6 @@ version "5.3.1" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9" -"@gimenete/type-writer@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@gimenete/type-writer/-/type-writer-0.1.3.tgz#2d4f26118b18d71f5b34ca24fdd6d1fd455c05b6" - dependencies: - camelcase "^5.0.0" - prettier "^1.13.7" - "@gulp-sourcemaps/identity-map@1.X": version "1.0.2" resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9" @@ -715,10 +628,9 @@ through2 "^2.0.3" "@octokit/rest@^15.2.6": - version "15.11.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.11.0.tgz#4851f24cf1ae26aa813c871cfc39a8287a42e8e8" + version "15.11.2" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.11.2.tgz#61bbfb6fa572eff0643cbe62ef695adac194ca8a" dependencies: - "@gimenete/type-writer" "^0.1.3" before-after-hook "^1.1.0" btoa-lite "^1.0.0" debug "^3.1.0" @@ -833,8 +745,8 @@ acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: xtend "^4.0.1" acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.2.tgz#91fa871883485d06708800318404e72bfb26dcc5" + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" after@0.8.2: version "0.8.2" @@ -868,14 +780,6 @@ ajv@^6.0.1, ajv@^6.5.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -926,11 +830,11 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -append-transform@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" dependencies: - default-require-extensions "^2.0.0" + default-require-extensions "^1.0.0" aproba@^1.0.3: version "1.2.0" @@ -1051,10 +955,6 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -1111,11 +1011,11 @@ async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" -async@1.5.2, async@^1.4.0: +async@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.4: +async@^2.1.4, async@^2.5.0: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: @@ -1221,9 +1121,9 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^23.4.2: - version "23.4.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.2.tgz#f276de67798a5d68f2d6e87ff518c2f6e1609877" +babel-jest@^23.4.2, babel-jest@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1" dependencies: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.2.0" @@ -1236,7 +1136,7 @@ babel-messages@^6.23.0: babel-plugin-istanbul@^4.1.6: version "4.1.6" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" dependencies: babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" @@ -1383,8 +1283,8 @@ better-assert@~1.0.0: callsite "1.0.0" big-integer@^1.6.17: - version "1.6.35" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.35.tgz#e093c3a50f63fb6bda0b5511c9425f1befcba74d" + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" binary-extensions@^1.0.0: version "1.11.0" @@ -1540,7 +1440,7 @@ browser-sync@^2.24.7: browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + resolved "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -1581,7 +1481,7 @@ browserify-des@^1.0.0: browserify-rsa@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + resolved "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" dependencies: bn.js "^4.1.0" randombytes "^2.0.1" @@ -1658,11 +1558,11 @@ browserify@^16.1.0, browserify@^16.2.2: xtend "^4.0.0" browserslist@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.1.0.tgz#81cbb8e52dfa09918f93c6e051d779cb7360785d" + version "4.1.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.1.1.tgz#328eb4ff1215b12df6589e9ab82f8adaa4fc8cd6" dependencies: - caniuse-lite "^1.0.30000878" - electron-to-chromium "^1.3.61" + caniuse-lite "^1.0.30000884" + electron-to-chromium "^1.3.62" node-releases "^1.0.0-alpha.11" bs-recipes@1.3.4: @@ -1765,10 +1665,6 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -1781,13 +1677,9 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" -camelcase@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" - -caniuse-lite@^1.0.30000878: - version "1.0.30000884" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000884.tgz#eb82a959698745033b26a4dcd34d89dba7cc6eb3" +caniuse-lite@^1.0.30000884: + version "1.0.30000885" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000885.tgz#e889e9f8e7e50e769f2a49634c932b8aee622984" capture-exit@^1.2.0: version "1.2.0" @@ -1803,13 +1695,6 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - chainsaw@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" @@ -1891,9 +1776,9 @@ chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" -ci-info@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.4.0.tgz#4841d53cad49f11b827b648ebde27a6e189b412f" +ci-info@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.5.1.tgz#17e8eb5de6f8b2b6038f0cbb714d410bfa9f3030" cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -1935,14 +1820,6 @@ cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -2043,14 +1920,14 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.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" + +commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" -compare-versions@^3.1.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" - component-bind@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -2108,12 +1985,14 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + dependencies: + safe-buffer "~5.1.1" convert-source-map@~1.1.0: version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" + resolved "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" cookie@0.3.1: version "0.3.1" @@ -2124,8 +2003,8 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" copyfiles@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.0.0.tgz#bbd78bb78e8fd6db5c67adf54249317b24560f2a" + version "2.1.0" + resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.1.0.tgz#0e2a4188162d6b2f3c5adfe34e9c0bd564d23164" dependencies: glob "^7.0.5" minimatch "^3.0.3" @@ -2134,10 +2013,6 @@ copyfiles@^2.0.0: through2 "^2.0.1" yargs "^11.0.0" -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - core-js@^2.4.0, core-js@^2.5.0: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -2165,7 +2040,7 @@ create-ecdh@^4.0.0: create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" dependencies: cipher-base "^1.0.1" inherits "^2.0.1" @@ -2175,7 +2050,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + resolved "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -2255,12 +2130,12 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" css@2.X, css@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.3.tgz#f861f4ba61e79bedc962aa548e5780fd95cbc6be" + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" dependencies: - inherits "^2.0.1" - source-map "^0.1.38" - source-map-resolve "^0.5.1" + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" urix "^0.1.0" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": @@ -2331,13 +2206,19 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" -debug@3.1.0, debug@3.X, debug@^3.1.0, debug@~3.1.0: +debug@3.1.0, debug@=3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: +debug@3.X, debug@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407" + dependencies: + ms "^2.1.1" + +decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2363,11 +2244,11 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-require-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" dependencies: - strip-bom "^3.0.0" + strip-bom "^2.0.0" defaults@^1.0.0: version "1.0.3" @@ -2494,7 +2375,7 @@ diff@^3.2.0: diffie-hellman@^5.0.0: version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + resolved "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" @@ -2578,9 +2459,9 @@ duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" -duplexer@~0.1.1: +duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" easy-extender@^2.3.4: version "2.3.4" @@ -2605,9 +2486,9 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -electron-to-chromium@^1.3.61: - version "1.3.62" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.62.tgz#2e8e2dc070c800ec8ce23ff9dfcceb585d6f9ed8" +electron-to-chromium@^1.3.62: + version "1.3.67" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.67.tgz#5e8f3ffac89b4b0402c7e1a565be06f3a109abbc" elliptic@^6.0.0: version "6.4.1" @@ -2629,12 +2510,6 @@ encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - dependencies: - iconv-lite "~0.4.13" - end-of-stream@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" @@ -2699,10 +2574,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" enzyme-adapter-react-16@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.4.0.tgz#c70aa6d085c5a6708b032406f5c73280d3f6a34b" + version "1.5.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.5.0.tgz#50af8d76a45fe0915de932bd95d34cdca75c0be3" dependencies: - enzyme-adapter-utils "^1.6.0" + enzyme-adapter-utils "^1.8.0" function.prototype.name "^1.1.0" object.assign "^4.1.0" object.values "^1.0.4" @@ -2710,17 +2585,17 @@ enzyme-adapter-react-16@^1.1.1: react-is "^16.4.2" react-test-renderer "^16.0.0-0" -enzyme-adapter-utils@^1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.6.1.tgz#f19acb8b23ddd21b2de99506b181e46138368f2a" +enzyme-adapter-utils@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.8.0.tgz#ee9f07250663a985f1f2caaf297720787da559f1" dependencies: function.prototype.name "^1.1.0" object.assign "^4.1.0" prop-types "^15.6.2" enzyme@^3.3.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.5.1.tgz#aad0cbd005fee4cfd800b6451b64112b5374da67" + version "3.6.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.6.0.tgz#d213f280a258f61e901bc663d4cc2d6fd9a9dec8" dependencies: array.prototype.flat "^1.2.1" cheerio "^1.0.0-rc.2" @@ -2783,8 +2658,8 @@ es6-iterator@^2.0.1, es6-iterator@~2.0.3: es6-symbol "^3.1.1" es6-promise@^4.0.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" + version "4.2.5" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" es6-promisify@^5.0.0: version "5.0.0" @@ -3005,16 +2880,17 @@ event-emitter@^0.3.5: es5-ext "~0.10.14" event-stream@~3.3.0: - version "3.3.4" - resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + version "3.3.6" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" + duplexer "^0.1.1" + flatmap-stream "^0.1.0" + from "^0.1.7" + map-stream "0.0.7" + pause-stream "^0.0.11" + split "^1.0.1" + stream-combiner "^0.2.2" + through "^2.3.8" eventemitter3@1.x.x: version "1.2.0" @@ -3089,14 +2965,14 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-23.5.0.tgz#18999a0eef8f8acf99023fde766d9c323c2562ed" +expect@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98" dependencies: ansi-styles "^3.2.0" - jest-diff "^23.5.0" + jest-diff "^23.6.0" jest-get-type "^22.1.0" - jest-matcher-utils "^23.5.0" + jest-matcher-utils "^23.6.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" @@ -3177,8 +3053,8 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" fast-xml-parser@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.12.0.tgz#84ddcd98ca005f94e99af3ac387adc32ffb239d8" + version "3.12.5" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.12.5.tgz#756e4da382f403f88990a62344add00948820fe0" dependencies: nimnjs "^1.3.2" @@ -3188,18 +3064,6 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.16: - version "0.8.17" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - fetch-mock@^6.5.0: version "6.5.2" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-6.5.2.tgz#b3842b305c13ea0f81c85919cfaa7de387adfa3e" @@ -3332,6 +3196,10 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flatmap-stream@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.0.tgz#ed54e01422cd29281800914fcb968d58b685d5f1" + flow-bin@^0.79.1: version "0.79.1" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.79.1.tgz#01c9f427baa6556753fa878c192d42e1ecb764b6" @@ -3357,10 +3225,10 @@ flow-typed@^2.5.1: yargs "^4.2.0" follow-redirects@^1.2.5: - version "1.5.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.7.tgz#a39e4804dacb90202bca76a9e2ac10433ca6a69a" + version "1.5.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.8.tgz#1dbfe13e45ad969f813e86c00e5296f525c885a1" dependencies: - debug "^3.1.0" + debug "=3.1.0" font-awesome@^4.7.0: version "4.7.0" @@ -3412,7 +3280,7 @@ fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" -from@~0: +from@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" @@ -3804,15 +3672,15 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handlebars@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" +handlebars@^4.0.3: + version "4.0.12" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" dependencies: - async "^1.4.0" + async "^2.5.0" optimist "^0.6.1" - source-map "^0.4.4" + source-map "^0.6.1" optionalDependencies: - uglify-js "^2.6" + uglify-js "^3.1.4" har-schema@^2.0.0: version "2.0.0" @@ -4017,7 +3885,7 @@ htmlparser2@^3.9.1: http-errors@1.6.3, http-errors@~1.6.2: version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" dependencies: depd "~1.1.2" inherits "2.0.3" @@ -4084,8 +3952,8 @@ i18next-xhr-backend@^1.4.3: resolved "https://registry.yarnpkg.com/i18next-xhr-backend/-/i18next-xhr-backend-1.5.1.tgz#50282610780c6a696d880dfa7f4ac1d01e8c3ad5" i18next@^11.4.0: - version "11.6.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-11.6.0.tgz#e0047aa3e3a0080f6f318426f90597cbb0d6ddd5" + version "11.9.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-11.9.0.tgz#c30c0a5e0a857124923a8dd1ce8f1df603e30c70" iconv-lite@0.4.23: version "0.4.23" @@ -4093,7 +3961,7 @@ iconv-lite@0.4.23: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" dependencies: @@ -4208,7 +4076,7 @@ interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -4266,10 +4134,10 @@ is-callable@^1.1.1, is-callable@^1.1.3, is-callable@^1.1.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" is-ci@^1.0.10: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.0.tgz#3f4a08d6303a09882cef3f0fb97439c5f5ce2d53" + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" dependencies: - ci-info "^1.3.0" + ci-info "^1.5.0" is-data-descriptor@^0.1.4: version "0.1.4" @@ -4487,7 +4355,7 @@ is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -4559,96 +4427,72 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.3.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.6.tgz#0c695f17e533131de8c49e0657175dcfd8af8a8f" + version "1.3.7" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa" dependencies: async "^2.1.4" - compare-versions "^3.1.0" fileset "^2.0.2" - istanbul-lib-coverage "^1.2.0" - istanbul-lib-hook "^1.2.0" - istanbul-lib-instrument "^2.1.0" - istanbul-lib-report "^1.1.4" - istanbul-lib-source-maps "^1.2.5" - istanbul-reports "^1.4.1" + istanbul-lib-coverage "^1.2.1" + istanbul-lib-hook "^1.2.2" + istanbul-lib-instrument "^1.10.2" + istanbul-lib-report "^1.1.5" + istanbul-lib-source-maps "^1.2.6" + istanbul-reports "^1.5.1" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" - -istanbul-lib-coverage@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#2aee0e073ad8c5f6a0b00e0dfbf52b4667472eda" - -istanbul-lib-hook@^1.2.0: +istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz#f614ec45287b2a8fc4f07f5660af787575601805" - dependencies: - append-transform "^1.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" -istanbul-lib-instrument@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" +istanbul-lib-hook@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.2.0" + istanbul-lib-coverage "^1.2.1" semver "^5.3.0" -istanbul-lib-instrument@^2.1.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-2.3.2.tgz#b287cbae2b5f65f3567b05e2e29b275eaf92d25e" +istanbul-lib-report@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" dependencies: - "@babel/generator" "7.0.0-beta.51" - "@babel/parser" "7.0.0-beta.51" - "@babel/template" "7.0.0-beta.51" - "@babel/traverse" "7.0.0-beta.51" - "@babel/types" "7.0.0-beta.51" - istanbul-lib-coverage "^2.0.1" - semver "^5.5.0" - -istanbul-lib-report@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5" - dependencies: - istanbul-lib-coverage "^1.2.0" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz#ffe6be4e7ab86d3603e4290d54990b14506fc9b1" +istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.2.0" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.0.tgz#c6c2867fa65f59eb7dcedb7f845dfc76aaee70f9" +istanbul-reports@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" dependencies: - handlebars "^4.0.11" + handlebars "^4.0.3" isurl@^1.0.0-alpha5: version "1.0.0" @@ -4663,9 +4507,9 @@ jest-changed-files@^23.4.2: dependencies: throat "^4.0.0" -jest-cli@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.5.0.tgz#d316b8e34a38a610a1efc4f0403d8ef8a55e4492" +jest-cli@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -4679,18 +4523,18 @@ jest-cli@^23.5.0: istanbul-lib-instrument "^1.10.1" istanbul-lib-source-maps "^1.2.4" jest-changed-files "^23.4.2" - jest-config "^23.5.0" + jest-config "^23.6.0" jest-environment-jsdom "^23.4.0" jest-get-type "^22.1.0" - jest-haste-map "^23.5.0" + jest-haste-map "^23.6.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" - jest-resolve-dependencies "^23.5.0" - jest-runner "^23.5.0" - jest-runtime "^23.5.0" - jest-snapshot "^23.5.0" + jest-resolve-dependencies "^23.6.0" + jest-runner "^23.6.0" + jest-runtime "^23.6.0" + jest-snapshot "^23.6.0" jest-util "^23.4.0" - jest-validate "^23.5.0" + jest-validate "^23.6.0" jest-watcher "^23.4.0" jest-worker "^23.2.0" micromatch "^2.3.11" @@ -4704,33 +4548,33 @@ jest-cli@^23.5.0: which "^1.2.12" yargs "^11.0.0" -jest-config@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.5.0.tgz#3770fba03f7507ee15f3b8867c742e48f31a9773" +jest-config@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d" dependencies: babel-core "^6.0.0" - babel-jest "^23.4.2" + babel-jest "^23.6.0" chalk "^2.0.1" glob "^7.1.1" jest-environment-jsdom "^23.4.0" jest-environment-node "^23.4.0" jest-get-type "^22.1.0" - jest-jasmine2 "^23.5.0" + jest-jasmine2 "^23.6.0" jest-regex-util "^23.3.0" - jest-resolve "^23.5.0" + jest-resolve "^23.6.0" jest-util "^23.4.0" - jest-validate "^23.5.0" + jest-validate "^23.6.0" micromatch "^2.3.11" - pretty-format "^23.5.0" + pretty-format "^23.6.0" -jest-diff@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.5.0.tgz#250651a433dd0050290a07642946cc9baaf06fba" +jest-diff@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d" dependencies: chalk "^2.0.1" diff "^3.2.0" jest-get-type "^22.1.0" - pretty-format "^23.5.0" + pretty-format "^23.6.0" jest-docblock@^23.2.0: version "23.2.0" @@ -4738,12 +4582,12 @@ jest-docblock@^23.2.0: dependencies: detect-newline "^2.1.0" -jest-each@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.5.0.tgz#77f7e2afe6132a80954b920006e78239862b10ba" +jest-each@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575" dependencies: chalk "^2.0.1" - pretty-format "^23.5.0" + pretty-format "^23.6.0" jest-environment-jsdom@^23.4.0: version "23.4.0" @@ -4764,9 +4608,9 @@ jest-get-type@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" -jest-haste-map@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065" +jest-haste-map@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16" dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" @@ -4777,22 +4621,22 @@ jest-haste-map@^23.5.0: micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.5.0.tgz#05fe7f1788e650eeb5a03929e6461ea2e9f3db53" +jest-jasmine2@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0" dependencies: babel-traverse "^6.0.0" chalk "^2.0.1" co "^4.6.0" - expect "^23.5.0" + expect "^23.6.0" is-generator-fn "^1.0.0" - jest-diff "^23.5.0" - jest-each "^23.5.0" - jest-matcher-utils "^23.5.0" + jest-diff "^23.6.0" + jest-each "^23.6.0" + jest-matcher-utils "^23.6.0" jest-message-util "^23.4.0" - jest-snapshot "^23.5.0" + jest-snapshot "^23.6.0" jest-util "^23.4.0" - pretty-format "^23.5.0" + pretty-format "^23.6.0" jest-junit@^5.1.0: version "5.1.0" @@ -4803,19 +4647,19 @@ jest-junit@^5.1.0: strip-ansi "^4.0.0" xml "^1.0.1" -jest-leak-detector@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.5.0.tgz#14ac2a785bd625160a2ea968fd5d98b7dcea3e64" +jest-leak-detector@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de" dependencies: - pretty-format "^23.5.0" + pretty-format "^23.6.0" -jest-matcher-utils@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.5.0.tgz#0e2ea67744cab78c9ab15011c4d888bdd3e49e2a" +jest-matcher-utils@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80" dependencies: chalk "^2.0.1" jest-get-type "^22.1.0" - pretty-format "^23.5.0" + pretty-format "^23.6.0" jest-message-util@^23.4.0: version "23.4.0" @@ -4835,42 +4679,42 @@ jest-regex-util@^23.3.0: version "23.3.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5" -jest-resolve-dependencies@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.5.0.tgz#10c4d135beb9d2256de1fedc7094916c3ad74af7" +jest-resolve-dependencies@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d" dependencies: jest-regex-util "^23.3.0" - jest-snapshot "^23.5.0" + jest-snapshot "^23.6.0" -jest-resolve@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.5.0.tgz#3b8e7f67e84598f0caf63d1530bd8534a189d0e6" +jest-resolve@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae" dependencies: browser-resolve "^1.11.3" chalk "^2.0.1" realpath-native "^1.0.0" -jest-runner@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.5.0.tgz#570f7a044da91648b5bb9b6baacdd511076c71d7" +jest-runner@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38" dependencies: exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^23.5.0" + jest-config "^23.6.0" jest-docblock "^23.2.0" - jest-haste-map "^23.5.0" - jest-jasmine2 "^23.5.0" - jest-leak-detector "^23.5.0" + jest-haste-map "^23.6.0" + jest-jasmine2 "^23.6.0" + jest-leak-detector "^23.6.0" jest-message-util "^23.4.0" - jest-runtime "^23.5.0" + jest-runtime "^23.6.0" jest-util "^23.4.0" jest-worker "^23.2.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.5.0.tgz#eb503525a196dc32f2f9974e3482d26bdf7b63ce" +jest-runtime@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082" dependencies: babel-core "^6.0.0" babel-plugin-istanbul "^4.1.6" @@ -4879,14 +4723,14 @@ jest-runtime@^23.5.0: exit "^0.1.2" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.11" - jest-config "^23.5.0" - jest-haste-map "^23.5.0" + jest-config "^23.6.0" + jest-haste-map "^23.6.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" - jest-resolve "^23.5.0" - jest-snapshot "^23.5.0" + jest-resolve "^23.6.0" + jest-snapshot "^23.6.0" jest-util "^23.4.0" - jest-validate "^23.5.0" + jest-validate "^23.6.0" micromatch "^2.3.11" realpath-native "^1.0.0" slash "^1.0.0" @@ -4898,19 +4742,19 @@ jest-serializer@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" -jest-snapshot@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.5.0.tgz#cc368ebd8513e1175e2a7277f37a801b7358ae79" +jest-snapshot@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a" dependencies: babel-types "^6.0.0" chalk "^2.0.1" - jest-diff "^23.5.0" - jest-matcher-utils "^23.5.0" + jest-diff "^23.6.0" + jest-matcher-utils "^23.6.0" jest-message-util "^23.4.0" - jest-resolve "^23.5.0" + jest-resolve "^23.6.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^23.5.0" + pretty-format "^23.6.0" semver "^5.5.0" jest-util@^23.4.0: @@ -4926,14 +4770,14 @@ jest-util@^23.4.0: slash "^1.0.0" source-map "^0.6.0" -jest-validate@^23.0.1, jest-validate@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.5.0.tgz#f5df8f761cf43155e1b2e21d6e9de8a2852d0231" +jest-validate@^23.0.1, jest-validate@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474" dependencies: chalk "^2.0.1" jest-get-type "^22.1.0" leven "^2.1.0" - pretty-format "^23.5.0" + pretty-format "^23.6.0" jest-watcher@^23.4.0: version "23.4.0" @@ -4950,11 +4794,11 @@ jest-worker@^23.2.0: merge-stream "^1.0.1" jest@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-23.5.0.tgz#80de353d156ea5ea4a7332f7962ac79135fbc62e" + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d" dependencies: import-local "^1.0.0" - jest-cli "^23.5.0" + jest-cli "^23.6.0" js-base64@^2.1.8: version "2.4.9" @@ -4964,14 +4808,14 @@ js-levenshtein@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.3.tgz#3ef627df48ec8cf24bacf05c0f184ff30ef413c5" -js-tokens@^3.0.0, js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + js-yaml@3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" @@ -5213,10 +5057,6 @@ labeled-stream-splicer@^2.0.0: isarray "^2.0.4" stream-splicer "^2.0.0" -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -5293,7 +5133,7 @@ load-json-file@^4.0.0: localtunnel@1.9.0: version "1.9.0" - resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.9.0.tgz#8ffecdcf8c8a14f62df1056cf9d54acbb0bb9a8f" + resolved "http://registry.npmjs.org/localtunnel/-/localtunnel-1.9.0.tgz#8ffecdcf8c8a14f62df1056cf9d54acbb0bb9a8f" dependencies: axios "0.17.1" debug "2.6.8" @@ -5308,8 +5148,8 @@ locate-path@^2.0.0: path-exists "^3.0.0" lodash-es@^4.17.5: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" lodash._basecopy@^3.0.0: version "3.0.1" @@ -5439,8 +5279,8 @@ lodash.templatesettings@^3.0.0: lodash.escape "^3.0.0" lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" lodash@~1.0.1: version "1.0.2" @@ -5450,10 +5290,6 @@ log-driver@1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5518,9 +5354,9 @@ map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" +map-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" map-visit@^1.0.0: version "1.0.0" @@ -5761,6 +5597,10 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + multipipe@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" @@ -5796,8 +5636,8 @@ nanomatch@^1.2.9: to-regex "^3.0.1" natives@^1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.4.tgz#2f0f224fc9a7dd53407c7667c84cf8dbe773de58" + version "1.1.5" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.5.tgz#3bdbdb4104023e5dd239b56fc7ef3d9a17acc6aa" natural-compare@^1.4.0: version "1.4.0" @@ -5814,8 +5654,8 @@ nearley@^2.7.10: semver "^5.4.1" needle@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.2.tgz#1120ca4c41f2fcc6976fd28a8968afe239929418" + version "2.2.3" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.3.tgz#c1b04da378cd634d8befe2de965dc2cfb0fd65ca" dependencies: debug "^2.1.2" iconv-lite "^0.4.4" @@ -5848,13 +5688,6 @@ nimnjs@^1.3.2: nimn-date-parser "^1.0.0" nimn_schema_builder "^1.0.0" -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-fetch@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" @@ -6305,7 +6138,7 @@ parents@^1.0.0, parents@^1.0.1: parse-asn1@^5.0.0: version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + resolved "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -6455,9 +6288,9 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" -pause-stream@0.0.11: +pause-stream@^0.0.11: version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + resolved "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" dependencies: through "~2.3" @@ -6549,9 +6382,9 @@ prettier@^1.13.7, prettier@^1.14.2: version "1.14.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.2.tgz#0ac1c6e1a90baa22a62925f41963c841983282f9" -pretty-format@^23.5.0: - version "23.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.5.0.tgz#0f9601ad9da70fe690a269cd3efca732c210687c" +pretty-format@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" @@ -6580,12 +6413,6 @@ progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - dependencies: - asap "~2.0.3" - prompts@^0.1.9: version "0.1.14" resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2" @@ -6616,7 +6443,7 @@ psl@^1.1.24: public-encrypt@^4.0.0: version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" + resolved "http://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" @@ -6717,25 +6544,25 @@ rc@^1.2.7: strip-json-comments "~2.0.1" react-dom@^16.4.2: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4" + version "16.5.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.0.tgz#57704e5718669374b182a17ea79a6d24922cb27d" dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" + prop-types "^15.6.2" + schedule "^0.3.0" react-i18next@^7.9.0: - version "7.11.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.11.1.tgz#eacaec8357f5557eaf290eae198025fe0a682603" + version "7.12.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.12.0.tgz#edaed1593fdfb34ddb47e4faf409cb36c8220d80" dependencies: hoist-non-react-statics "^2.3.1" html-parse-stringify2 "2.0.1" prop-types "^15.6.0" -react-is@^16.4.2: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88" +react-is@^16.4.2, react-is@^16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.0.tgz#2ec7c192709698591efe13722fab3ef56144ba55" react-jss@^8.6.0: version "8.6.1" @@ -6797,22 +6624,22 @@ react-router@^4.2.0, react-router@^4.3.1: warning "^4.0.1" react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.2.tgz#4e03eca9359bb3210d4373f7547d1364218ef74e" + version "16.5.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.0.tgz#1aeca0edc4f27f63265dcaed80ba82e11e762f56" dependencies: - fbjs "^0.8.16" object-assign "^4.1.1" - prop-types "^15.6.0" - react-is "^16.4.2" + prop-types "^15.6.2" + react-is "^16.5.0" + schedule "^0.3.0" react@^16.4.2: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f" + version "16.5.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.5.0.tgz#f2c1e754bf9751a549d9c6d9aca41905beb56575" dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" + prop-types "^15.6.2" + schedule "^0.3.0" read-only-stream@^2.0.0: version "2.0.0" @@ -6834,13 +6661,6 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -6867,7 +6687,7 @@ read-pkg@^3.0.0: "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.31: version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -6876,7 +6696,7 @@ read-pkg@^3.0.0: readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -6888,7 +6708,7 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable readable-stream@~1.1.9: version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -6897,7 +6717,7 @@ readable-stream@~1.1.9: readable-stream@~2.1.5: version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: buffer-shims "^1.0.0" core-util-is "~1.0.0" @@ -6917,8 +6737,8 @@ readdirp@^2.0.0: set-immediate-shim "^1.0.1" realpath-native@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633" + version "1.0.2" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560" dependencies: util.promisify "^1.0.0" @@ -7222,12 +7042,6 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - dependencies: - align-text "^0.1.1" - rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" @@ -7263,8 +7077,8 @@ rx@4.1.0: resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" rxjs@^6.1.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.1.tgz#878a1a8c64b8a5da11dcf74b5033fe944cdafb84" + version "6.3.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.2.tgz#6a688b16c4e6e980e62ea805ec30648e1c60907f" dependencies: tslib "^1.9.0" @@ -7310,6 +7124,12 @@ sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" +schedule@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.3.0.tgz#1be2ab2fc2e768536269ce7326efb478d6c045e8" + dependencies: + object-assign "^4.1.1" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -7402,7 +7222,7 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.5, setimmediate@~1.0.4: +setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -7412,14 +7232,14 @@ setprototypeof@1.1.0: sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + resolved "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" shasum@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" + resolved "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" dependencies: json-stable-stringify "~0.0.0" sha.js "~2.4.4" @@ -7575,7 +7395,7 @@ socket.io@2.1.1: socket.io-client "2.1.1" socket.io-parser "~3.2.0" -source-map-resolve@^0.5.0, source-map-resolve@^0.5.1: +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" dependencies: @@ -7602,23 +7422,17 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.1.38: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.4.2, source-map@^0.4.4: +source-map@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@^0.6.0, source-map@~0.6.0, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -7645,8 +7459,8 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -7654,9 +7468,9 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" +split@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" dependencies: through "2" @@ -7726,11 +7540,12 @@ stream-combiner2@^1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" +stream-combiner@^0.2.2: + version "0.2.2" + resolved "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" dependencies: duplexer "~0.1.1" + through "~2.3.4" stream-consume@~0.1.0: version "0.1.1" @@ -7933,12 +7748,13 @@ tar@^4: yallist "^3.0.2" test-exclude@^4.2.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.2.tgz#8b67aa8408f84afc225b06669e25c510f8582820" + version "4.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" dependencies: arrify "^1.0.1" - minimatch "^3.0.4" - read-pkg-up "^3.0.0" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" require-main-filename "^1.0.1" text-table@^0.2.0: @@ -7979,9 +7795,9 @@ through2@^0.6.1: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" tildify@^1.0.0: version "1.2.0" @@ -8135,30 +7951,13 @@ ua-parser-js@0.7.17: version "0.7.17" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" -ua-parser-js@^0.7.18: - version "0.7.18" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" - -uglify-js@^2.6: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-js@^3.0.5: +uglify-js@^3.0.5, uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" dependencies: commander "~2.17.1" source-map "~0.6.1" -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" @@ -8464,10 +8263,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.23" -whatwg-fetch@>=0.10.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - whatwg-mimetype@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" @@ -8508,18 +8303,10 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" @@ -8633,7 +8420,7 @@ yargs-parser@^9.0.2: yargs@6.4.0: version "6.4.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.4.0.tgz#816e1a866d5598ccf34e5596ddce22d92da490d4" + resolved "http://registry.npmjs.org/yargs/-/yargs-6.4.0.tgz#816e1a866d5598ccf34e5596ddce22d92da490d4" dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -8652,7 +8439,7 @@ yargs@6.4.0: yargs@6.6.0: version "6.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + resolved "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -8670,7 +8457,7 @@ yargs@6.6.0: yargs@^11.0.0: version "11.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" + resolved "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -8687,7 +8474,7 @@ yargs@^11.0.0: yargs@^4.2.0: version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + resolved "http://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" dependencies: cliui "^3.2.0" decamelize "^1.1.1" @@ -8722,15 +8509,6 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" From e24c3e14a37e7b62272ccb710c441181867cf5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 13 Sep 2018 15:31:52 +0200 Subject: [PATCH 03/96] reformat --- .../src/repos/changesets/containers/ChangesetView.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scm-ui/src/repos/changesets/containers/ChangesetView.js b/scm-ui/src/repos/changesets/containers/ChangesetView.js index a3d8effc7d..c3b7c248ae 100644 --- a/scm-ui/src/repos/changesets/containers/ChangesetView.js +++ b/scm-ui/src/repos/changesets/containers/ChangesetView.js @@ -1,17 +1,11 @@ //@flow import React from "react"; -type Props = { - -} +type Props = {}; class ChangesetView extends React.Component { render() { - return ( -
-Hallo! Changesets here! -
- ); + return
Hallo! Changesets here!
; } } From 6aeeca89f3621ef17116f355923d2e0560a4a56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 18 Sep 2018 11:32:56 +0200 Subject: [PATCH 04/96] resolve merge conflicts --- scm-ui/src/repos/containers/RepositoryRoot.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 2f72275120..3368d94bb3 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -7,15 +7,9 @@ import { getRepository, isFetchRepoPending } from "../modules/repos"; -<<<<<<< working copy import { connect } from "react-redux"; import { Route, Switch } from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; -======= -import {connect} from "react-redux"; -import {Route} from "react-router-dom"; -import type {Repository} from "@scm-manager/ui-types"; ->>>>>>> merge rev import { Page, Loading, @@ -31,11 +25,8 @@ import Edit from "../containers/Edit"; import type {History} from "history"; import EditNavLink from "../components/EditNavLink"; -<<<<<<< working copy import ChangesetView from "../changesets/containers/ChangesetView"; -======= import Changesets from "../../changesets/containers/Changesets"; ->>>>>>> merge rev type Props = { namespace: string, From c57c15289883779c9fab6bab6a1dc66683639a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 18 Sep 2018 16:40:03 +0200 Subject: [PATCH 05/96] add route for detailed changeset view --- .../src/changesets/components/ChangesetRow.js | 36 +++++++++----- .../src/changesets/containers/Changesets.js | 47 ++++++++++++++++--- scm-ui/src/repos/containers/RepositoryRoot.js | 10 ---- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index d0be206866..3750b8b431 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -1,28 +1,38 @@ import React from "react" import type { Changeset } from "@scm-manager/ui-types" import {ExtensionPoint} from "@scm-manager/ui-extensions"; +import {Link} from "react-router-dom"; +import {History} from "history"; +import { NavLink } from "@scm-manager/ui-components"; +import {withRouter} from "react-router-dom"; + type Props = { - changeset: Changeset + changeset: Changeset, + location: any } class ChangesetRow extends React.Component { + render() { const { changeset } = this.props; // todo: i18n - return - - -

{changeset.description}

-

Changeset { changeset.id } commited at { changeset.date }

-

{changeset.author.name} <{changeset.author.mail}>

- + return ( + + + +

+

Changeset { changeset.id } commited at { changeset.date }

+

{changeset.author.name} <{changeset.author.mail}>

+ + ); + } } -export default ChangesetRow; +export default withRouter(ChangesetRow); diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 64b56fa559..664c206fdd 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -11,7 +11,8 @@ import {fetchBranchesByNamespaceAndName, getBranchNames} from "../../repos/modul import type {Repository} from "@scm-manager/ui-types"; import ChangesetTable from "../components/ChangesetTable"; import DropDown from "../components/DropDown"; -import {withRouter} from "react-router-dom"; +import {Route, withRouter} from "react-router-dom"; +import ChangesetView from "../../repos/changesets/containers/ChangesetView"; type Props = { repository: Repository, @@ -38,18 +39,52 @@ class Changesets extends React.Component { this.props.fetchBranchesByNamespaceAndName(namespace, name); } + matchedUrl = () => { + return this.props.match.url; + }; render() { - const {changesets, loading, error} = this.props; + const url = this.matchedUrl(); + + const {changesets, loading, error, repository} = this.props; if (loading || !changesets) { return } - return
- - {this.renderContent()} -
+ return( +
+ + + } + /> + } + /> +
+ ) + } + renderChangesets = () => { + return( +
+ + {this.renderContent()} +
+ ) + } renderContent = () => { const branch = this.props.match.params.branch; const {changesets, branchNames} = this.props; diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 3368d94bb3..8c079076ee 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -104,19 +104,9 @@ class RepositoryRoot extends React.Component { component={() => } /> } /> - } - /> - } - />
From a2954864b7634cafd1294e52322861f2b356e102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 18 Sep 2018 16:43:40 +0200 Subject: [PATCH 06/96] add commit tag to url to decide between changeset view and detailed changeset view --- scm-ui/src/changesets/components/ChangesetRow.js | 2 +- scm-ui/src/changesets/containers/Changesets.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index 3750b8b431..5f88294cb1 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -26,7 +26,7 @@ class ChangesetRow extends React.Component { renderAll={true} props={{ changeset }} /> -

+

Changeset { changeset.id } commited at { changeset.date }

{changeset.author.name} <{changeset.author.mail}>

diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 664c206fdd..024376692b 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -63,12 +63,12 @@ class Changesets extends React.Component { /> } /> } />
@@ -90,11 +90,11 @@ class Changesets extends React.Component { const {changesets, branchNames} = this.props; if (branchNames) { - return
+ return (
this.branchChanged(branch)}/> -
; +
); } return From 60347ac34ae4ad56c134ed562b7f571cecc34c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 08:22:50 +0200 Subject: [PATCH 07/96] change routing that selector is working --- .../src/changesets/containers/Changesets.js | 158 ++++++++++-------- scm-ui/src/repos/containers/RepositoryRoot.js | 36 ++-- 2 files changed, 111 insertions(+), 83 deletions(-) diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 024376692b..9da1fd05f4 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -1,26 +1,35 @@ -import React from "react" -import {connect} from "react-redux"; -import {ErrorNotification, Loading} from "@scm-manager/ui-components"; +import React from "react"; +import { connect } from "react-redux"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import { - fetchChangesetsByNamespaceAndName, fetchChangesetsByNamespaceNameAndBranch, - getChangesets, getFetchChangesetsFailure, isFetchChangesetsPending, + fetchChangesetsByNamespaceAndName, + fetchChangesetsByNamespaceNameAndBranch, + getChangesets, + getFetchChangesetsFailure, + isFetchChangesetsPending } from "../modules/changesets"; -import type {History} from "history"; -import {fetchBranchesByNamespaceAndName, getBranchNames} from "../../repos/modules/branches"; -import type {Repository} from "@scm-manager/ui-types"; +import type { History } from "history"; +import { + fetchBranchesByNamespaceAndName, + getBranchNames +} from "../../repos/modules/branches"; +import type { Repository } from "@scm-manager/ui-types"; import ChangesetTable from "../components/ChangesetTable"; import DropDown from "../components/DropDown"; -import {Route, withRouter} from "react-router-dom"; +import { Route, withRouter } from "react-router-dom"; import ChangesetView from "../../repos/changesets/containers/ChangesetView"; type Props = { repository: Repository, branchName: string, history: History, - fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => void -} - + fetchChangesetsByNamespaceNameAndBranch: ( + namespace: string, + name: string, + branch: string + ) => void +}; class Changesets extends React.Component { constructor(props) { @@ -29,10 +38,14 @@ class Changesets extends React.Component { } componentDidMount() { - const {namespace, name} = this.props.repository; + const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; if (branchName) { - this.props.fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); + this.props.fetchChangesetsByNamespaceNameAndBranch( + namespace, + name, + branchName + ); } else { this.props.fetchChangesetsByNamespaceAndName(namespace, name); } @@ -45,77 +58,76 @@ class Changesets extends React.Component { render() { const url = this.matchedUrl(); - const {changesets, loading, error, repository} = this.props; + const { changesets, loading, error, repository } = this.props; if (loading || !changesets) { - return + return ; } - return( + return (
- - - } - /> - } - /> -
- ) - - + + } + /> + + ); } renderChangesets = () => { - return( + return (
- - {this.renderContent()} -
- ) - } + + {this.renderContent()} + + ); + }; renderContent = () => { const branch = this.props.match.params.branch; - const {changesets, branchNames} = this.props; + const { changesets, branchNames } = this.props; if (branchNames) { - return (
- this.branchChanged(branch)}/> - -
); + return ( +
+ this.branchChanged(branch)} + /> + +
+ ); } - return + return ; }; - branchChanged = (branchName: string) => { - const {history, repository} = this.props; - history.push(`/repo/${repository.namespace}/${repository.name}/history/${branchName}`); + const { history, repository } = this.props; + history.push( + `/repo/${repository.namespace}/${repository.name}/history/${branchName}` + ); }; } - const mapStateToProps = (state, ownProps: Props) => { - const {namespace, name} = ownProps.repository; + const { namespace, name } = ownProps.repository; return { loading: isFetchChangesetsPending(namespace, name, state), - changesets: getChangesets(state, namespace, name, ownProps.match.params.branch), + changesets: getChangesets( + state, + namespace, + name, + ownProps.match.params.branch + ), branchNames: getBranchNames(namespace, name, state), - error: getFetchChangesetsFailure(state, namespace, name, ownProps.match.params.branch) - } + error: getFetchChangesetsFailure( + state, + namespace, + name, + ownProps.match.params.branch + ) + }; }; const mapDispatchToProps = dispatch => { @@ -123,16 +135,24 @@ const mapDispatchToProps = dispatch => { fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); }, - fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => { - dispatch(fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch)); + fetchChangesetsByNamespaceNameAndBranch: ( + namespace: string, + name: string, + branch: string + ) => { + dispatch( + fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch) + ); }, fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchBranchesByNamespaceAndName(namespace, name)); } - } + }; }; -export default withRouter(connect( - mapStateToProps, - mapDispatchToProps -)(Changesets)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(Changesets) +); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 8c079076ee..5eef181e56 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -18,12 +18,12 @@ import { NavLink, Section } from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; +import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import DeleteNavAction from "../components/DeleteNavAction"; import Edit from "../containers/Edit"; -import type {History} from "history"; +import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; import ChangesetView from "../changesets/containers/ChangesetView"; import Changesets from "../../changesets/containers/Changesets"; @@ -47,7 +47,7 @@ type Props = { class RepositoryRoot extends React.Component { componentDidMount() { - const {fetchRepo, namespace, name} = this.props; + const { fetchRepo, namespace, name } = this.props; fetchRepo(namespace, name); } @@ -72,7 +72,7 @@ class RepositoryRoot extends React.Component { }; render() { - const {loading, error, repository, t} = this.props; + const { loading, error, repository, t } = this.props; if (error) { return ( @@ -85,7 +85,7 @@ class RepositoryRoot extends React.Component { } if (!repository || loading) { - return ; + return ; } const url = this.matchedUrl(); @@ -97,27 +97,35 @@ class RepositoryRoot extends React.Component { } + component={() => } /> } + component={() => } /> } + component={() => } + /> + } />
- - - + + +
- - + +
@@ -128,7 +136,7 @@ class RepositoryRoot extends React.Component { } const mapStateToProps = (state, ownProps) => { - const {namespace, name} = ownProps.match.params; + const { namespace, name } = ownProps.match.params; const repository = getRepository(state, namespace, name); const loading = isFetchRepoPending(state, namespace, name); const error = getFetchRepoFailure(state, namespace, name); From 4eb0ee367b922f0a8a7dae002cb01977ca80c32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 08:32:02 +0200 Subject: [PATCH 08/96] get id --- .../changesets/containers/ChangesetView.js | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/changesets/containers/ChangesetView.js b/scm-ui/src/repos/changesets/containers/ChangesetView.js index c3b7c248ae..e00b62aa14 100644 --- a/scm-ui/src/repos/changesets/containers/ChangesetView.js +++ b/scm-ui/src/repos/changesets/containers/ChangesetView.js @@ -1,12 +1,40 @@ //@flow import React from "react"; +import { connect } from "react-redux"; +import { withRouter } from "react-router-dom"; -type Props = {}; +type Props = { + id: string +}; + +class ChangesetView extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + + componentDidMount() { + const id = this.props.match.params.id; + } -class ChangesetView extends React.Component { render() { - return
Hallo! Changesets here!
; + const id = this.props.match.params.id; + + return
Hallo! Changesets here! {id}
; } } -export default ChangesetView; +const mapStateToProps = (state, ownProps: Props) => { + return null; +}; + +const mapDispatchToProps = dispatch => { + return null; +}; + +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(ChangesetView) +); From d7d389320586aad2c8c17ef4d39489b825ca3814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 09:25:19 +0200 Subject: [PATCH 09/96] add fetch changeset mock --- scm-ui/src/changesets/modules/changesets.js | 236 ++++++++++++++++---- 1 file changed, 198 insertions(+), 38 deletions(-) diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 498f6b3aea..3f2819fc0a 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -1,44 +1,153 @@ // @flow -import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; -import {apiClient} from "@scm-manager/ui-components"; -import {isPending} from "../../modules/pending"; -import {getFailure} from "../../modules/failure"; +import { + FAILURE_SUFFIX, + PENDING_SUFFIX, + SUCCESS_SUFFIX +} from "../../modules/types"; +import { apiClient } from "@scm-manager/ui-components"; +import { isPending } from "../../modules/pending"; +import { getFailure } from "../../modules/failure"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; +/****added for detailed view of changesets****/ + +export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; +export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; +export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; +export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; + +/********/ + const REPO_URL = "repositories"; +/****added for detailed view of changesets****/ + +export function fetchChangesetIfNeeded(state: Object, namespace: string, repoName: string, id: string) { + return function(dispatch) { + if (shouldFetchChangeset(state, namespace, repoName, id)) { + dispatch(fetchChangeset(url)); + } + }; +} + +export function shouldFetchChangeset(state: Object, namespace: string, repoName: string, id: string) { + // decide if changeset should be fetched here + return true; +} + +function fetchChangeset(namespace: string, repoName: string, id: string) { + return function(dispatch) { + dispatch(fetchChangesetPending(namespace, repoName, id)); + return apiClient + .get(url) + .then(response => response.json()) + .then(json => dispatch(fetchChangesetSuccess(namespace, repoName, id))) + .catch(err => { + dispatch(fetchChangesetFailure(namespace, repoName, id, err)); + }); + }; +} + +export function fetchChangesetPending( + namespace: string, repoName: string, id: string +): Action { + return { + type: FETCH_CHANGESET_PENDING, + payload: { + namespace, + repoName, + id + }, + itemId: createItemId(namespace, repoName) + "/" + id + }; +} + +export function fetchChangesetSuccess( + namespace: string, repoName: string, id: string +): Action { + return { + type: FETCH_CHANGESET_SUCCESS, + payload: { namespace, repoName, id }, + itemId: createItemId(namespace, repoName) + "/" + id + }; +} + +function fetchChangesetFailure( + namespace: string, + name: string, + id: string, + error: Error +): Action { + return { + type: FETCH_CHANGESET_FAILURE, + payload: { + namespace, + name, + id, + error + }, + itemId: createItemId(namespace, repoName) + "/" + id + }; + +/********/ // actions -export function fetchChangesetsByNamespaceAndName(namespace: string, name: string) { - return function (dispatch: any) { +export function fetchChangesetsByNamespaceAndName( + namespace: string, + name: string +) { + return function(dispatch: any) { dispatch(fetchChangesetsPending(namespace, name)); - return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/changesets").then(response => response.json()) + return apiClient + .get(REPO_URL + "/" + namespace + "/" + name + "/changesets") + .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name)) - }).catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, cause)) + dispatch(fetchChangesetsSuccess(data, namespace, name)); }) - } + .catch(cause => { + dispatch(fetchChangesetsFailure(namespace, name, cause)); + }); + }; } -export function fetchChangesetsByNamespaceNameAndBranch(namespace: string, name: string, branch: string) { - return function (dispatch: any) { +export function fetchChangesetsByNamespaceNameAndBranch( + namespace: string, + name: string, + branch: string +) { + return function(dispatch: any) { dispatch(fetchChangesetsPending(namespace, name, branch)); - return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/branches/" + branch + "/changesets").then(response => response.json()) + return apiClient + .get( + REPO_URL + + "/" + + namespace + + "/" + + name + + "/branches/" + + branch + + "/changesets" + ) + .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name, branch)) - }).catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, branch, cause)) + dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); }) - } + .catch(cause => { + dispatch(fetchChangesetsFailure(namespace, name, branch, cause)); + }); + }; } -export function fetchChangesetsPending(namespace: string, name: string, branch?: string): Action { +export function fetchChangesetsPending( + namespace: string, + name: string, + branch?: string +): Action { return { type: FETCH_CHANGESETS_PENDING, payload: { @@ -47,18 +156,28 @@ export function fetchChangesetsPending(namespace: string, name: string, branch?: branch }, itemId: createItemId(namespace, name, branch) - } + }; } -export function fetchChangesetsSuccess(collection: any, namespace: string, name: string, branch?: string): Action { +export function fetchChangesetsSuccess( + collection: any, + namespace: string, + name: string, + branch?: string +): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace, name, branch}, + payload: { collection, namespace, name, branch }, itemId: createItemId(namespace, name, branch) - } + }; } -function fetchChangesetsFailure(namespace: string, name: string, branch?: string, error: Error): Action { +function fetchChangesetsFailure( + namespace: string, + name: string, + branch?: string, + error: Error +): Action { return { type: FETCH_CHANGESETS_FAILURE, payload: { @@ -68,10 +187,14 @@ function fetchChangesetsFailure(namespace: string, name: string, branch?: string error }, itemId: createItemId(namespace, name, branch) - } + }; } -function createItemId(namespace: string, name: string, branch?: string): string { +function createItemId( + namespace: string, + name: string, + branch?: string +): string { let itemId = namespace + "/" + name; if (branch && branch !== "") { itemId = itemId + "/" + branch; @@ -80,16 +203,27 @@ function createItemId(namespace: string, name: string, branch?: string): string } // reducer -export default function reducer(state: any = {}, action: Action = {type: "UNKNOWN"}): Object { +export default function reducer( + state: any = {}, + action: Action = { type: "UNKNOWN" } +): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: - const {namespace, name, branch} = action.payload; + const { namespace, name, branch } = action.payload; const key = createItemId(namespace, name, branch); - let oldChangesets = {[key]: {}}; + let oldChangesets = { [key]: {} }; if (state[key] !== undefined) { - oldChangesets[key] = state[key] + oldChangesets[key] = state[key]; } - return {...state, [key]: {byId: extractChangesetsByIds(action.payload.collection, oldChangesets[key].byId)}}; + return { + ...state, + [key]: { + byId: extractChangesetsByIds( + action.payload.collection, + oldChangesets[key].byId + ) + } + }; default: return state; } @@ -111,7 +245,11 @@ function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { } //selectors -export function getChangesetsForNamespaceAndNameFromState(namespace: string, name: string, state: Object) { +export function getChangesetsForNamespaceAndNameFromState( + namespace: string, + name: string, + state: Object +) { const key = createItemId(namespace, name); if (!state.changesets[key]) { return null; @@ -119,7 +257,12 @@ export function getChangesetsForNamespaceAndNameFromState(namespace: string, nam return Object.values(state.changesets[key].byId); } -export function getChangesets(state: Object, namespace: string, name: string, branch: string) { +export function getChangesets( + state: Object, + namespace: string, + name: string, + branch: string +) { const key = createItemId(namespace, name, branch); if (!state.changesets[key]) { return null; @@ -127,11 +270,28 @@ export function getChangesets(state: Object, namespace: string, name: string, br return Object.values(state.changesets[key].byId); } -export function isFetchChangesetsPending(state: Object, namespace: string, name: string, branch?: string) { - return isPending(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)) +export function isFetchChangesetsPending( + state: Object, + namespace: string, + name: string, + branch?: string +) { + return isPending( + state, + FETCH_CHANGESETS, + createItemId(namespace, name, branch) + ); } -export function getFetchChangesetsFailure(state: Object, namespace: string, name: string, branch?: string) { - return getFailure(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)); +export function getFetchChangesetsFailure( + state: Object, + namespace: string, + name: string, + branch?: string +) { + return getFailure( + state, + FETCH_CHANGESETS, + createItemId(namespace, name, branch) + ); } - From 33fc15b99c0f159510df94894ab79b5c69dd8633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 10:06:38 +0200 Subject: [PATCH 10/96] resolve further merging conflicts --- .../src/changesets/components/ChangesetRow.js | 38 --- .../src/changesets/containers/Changesets.js | 158 ---------- scm-ui/src/changesets/modules/changesets.js | 297 ------------------ .../repos/changesets/modules/changesets.js | 0 .../changesets/modules/changesets.test.js | 0 .../src/repos/changesets/types/Changeset.js | 13 - .../containers/ChangesetView.js | 0 scm-ui/src/repos/containers/Changesets.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 9 +- scm-ui/src/repos/modules/changesets.js | 97 +++++- 10 files changed, 103 insertions(+), 511 deletions(-) delete mode 100644 scm-ui/src/changesets/components/ChangesetRow.js delete mode 100644 scm-ui/src/changesets/containers/Changesets.js delete mode 100644 scm-ui/src/changesets/modules/changesets.js delete mode 100644 scm-ui/src/repos/changesets/modules/changesets.js delete mode 100644 scm-ui/src/repos/changesets/modules/changesets.test.js delete mode 100644 scm-ui/src/repos/changesets/types/Changeset.js rename scm-ui/src/repos/{changesets => }/containers/ChangesetView.js (100%) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js deleted file mode 100644 index 5f88294cb1..0000000000 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react" -import type { Changeset } from "@scm-manager/ui-types" -import {ExtensionPoint} from "@scm-manager/ui-extensions"; -import {Link} from "react-router-dom"; -import {History} from "history"; -import { NavLink } from "@scm-manager/ui-components"; -import {withRouter} from "react-router-dom"; - - -type Props = { - changeset: Changeset, - location: any -} - -class ChangesetRow extends React.Component { - - - render() { - const { changeset } = this.props; - // todo: i18n - return ( - - - -

-

Changeset { changeset.id } commited at { changeset.date }

-

{changeset.author.name} <{changeset.author.mail}>

- - ); - - } -} - -export default withRouter(ChangesetRow); diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js deleted file mode 100644 index 9da1fd05f4..0000000000 --- a/scm-ui/src/changesets/containers/Changesets.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import { ErrorNotification, Loading } from "@scm-manager/ui-components"; - -import { - fetchChangesetsByNamespaceAndName, - fetchChangesetsByNamespaceNameAndBranch, - getChangesets, - getFetchChangesetsFailure, - isFetchChangesetsPending -} from "../modules/changesets"; -import type { History } from "history"; -import { - fetchBranchesByNamespaceAndName, - getBranchNames -} from "../../repos/modules/branches"; -import type { Repository } from "@scm-manager/ui-types"; -import ChangesetTable from "../components/ChangesetTable"; -import DropDown from "../components/DropDown"; -import { Route, withRouter } from "react-router-dom"; -import ChangesetView from "../../repos/changesets/containers/ChangesetView"; - -type Props = { - repository: Repository, - branchName: string, - history: History, - fetchChangesetsByNamespaceNameAndBranch: ( - namespace: string, - name: string, - branch: string - ) => void -}; - -class Changesets extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - - componentDidMount() { - const { namespace, name } = this.props.repository; - const branchName = this.props.match.params.branch; - if (branchName) { - this.props.fetchChangesetsByNamespaceNameAndBranch( - namespace, - name, - branchName - ); - } else { - this.props.fetchChangesetsByNamespaceAndName(namespace, name); - } - this.props.fetchBranchesByNamespaceAndName(namespace, name); - } - - matchedUrl = () => { - return this.props.match.url; - }; - render() { - const url = this.matchedUrl(); - - const { changesets, loading, error, repository } = this.props; - if (loading || !changesets) { - return ; - } - return ( -
- - } - /> -
- ); - } - - renderChangesets = () => { - return ( -
- - {this.renderContent()} -
- ); - }; - renderContent = () => { - const branch = this.props.match.params.branch; - const { changesets, branchNames } = this.props; - - if (branchNames) { - return ( -
- this.branchChanged(branch)} - /> - -
- ); - } - - return ; - }; - - branchChanged = (branchName: string) => { - const { history, repository } = this.props; - history.push( - `/repo/${repository.namespace}/${repository.name}/history/${branchName}` - ); - }; -} - -const mapStateToProps = (state, ownProps: Props) => { - const { namespace, name } = ownProps.repository; - return { - loading: isFetchChangesetsPending(namespace, name, state), - changesets: getChangesets( - state, - namespace, - name, - ownProps.match.params.branch - ), - branchNames: getBranchNames(namespace, name, state), - error: getFetchChangesetsFailure( - state, - namespace, - name, - ownProps.match.params.branch - ) - }; -}; - -const mapDispatchToProps = dispatch => { - return { - fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); - }, - fetchChangesetsByNamespaceNameAndBranch: ( - namespace: string, - name: string, - branch: string - ) => { - dispatch( - fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch) - ); - }, - fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchBranchesByNamespaceAndName(namespace, name)); - } - }; -}; - -export default withRouter( - connect( - mapStateToProps, - mapDispatchToProps - )(Changesets) -); diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js deleted file mode 100644 index 3f2819fc0a..0000000000 --- a/scm-ui/src/changesets/modules/changesets.js +++ /dev/null @@ -1,297 +0,0 @@ -// @flow - -import { - FAILURE_SUFFIX, - PENDING_SUFFIX, - SUCCESS_SUFFIX -} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; -import { isPending } from "../../modules/pending"; -import { getFailure } from "../../modules/failure"; - -export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; -export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; -export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; -export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; - -/****added for detailed view of changesets****/ - -export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; -export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; -export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; -export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; - -/********/ - -const REPO_URL = "repositories"; - -/****added for detailed view of changesets****/ - -export function fetchChangesetIfNeeded(state: Object, namespace: string, repoName: string, id: string) { - return function(dispatch) { - if (shouldFetchChangeset(state, namespace, repoName, id)) { - dispatch(fetchChangeset(url)); - } - }; -} - -export function shouldFetchChangeset(state: Object, namespace: string, repoName: string, id: string) { - // decide if changeset should be fetched here - return true; -} - -function fetchChangeset(namespace: string, repoName: string, id: string) { - return function(dispatch) { - dispatch(fetchChangesetPending(namespace, repoName, id)); - return apiClient - .get(url) - .then(response => response.json()) - .then(json => dispatch(fetchChangesetSuccess(namespace, repoName, id))) - .catch(err => { - dispatch(fetchChangesetFailure(namespace, repoName, id, err)); - }); - }; -} - -export function fetchChangesetPending( - namespace: string, repoName: string, id: string -): Action { - return { - type: FETCH_CHANGESET_PENDING, - payload: { - namespace, - repoName, - id - }, - itemId: createItemId(namespace, repoName) + "/" + id - }; -} - -export function fetchChangesetSuccess( - namespace: string, repoName: string, id: string -): Action { - return { - type: FETCH_CHANGESET_SUCCESS, - payload: { namespace, repoName, id }, - itemId: createItemId(namespace, repoName) + "/" + id - }; -} - -function fetchChangesetFailure( - namespace: string, - name: string, - id: string, - error: Error -): Action { - return { - type: FETCH_CHANGESET_FAILURE, - payload: { - namespace, - name, - id, - error - }, - itemId: createItemId(namespace, repoName) + "/" + id - }; - -/********/ - -// actions -export function fetchChangesetsByNamespaceAndName( - namespace: string, - name: string -) { - return function(dispatch: any) { - dispatch(fetchChangesetsPending(namespace, name)); - return apiClient - .get(REPO_URL + "/" + namespace + "/" + name + "/changesets") - .then(response => response.json()) - .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name)); - }) - .catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, cause)); - }); - }; -} - -export function fetchChangesetsByNamespaceNameAndBranch( - namespace: string, - name: string, - branch: string -) { - return function(dispatch: any) { - dispatch(fetchChangesetsPending(namespace, name, branch)); - return apiClient - .get( - REPO_URL + - "/" + - namespace + - "/" + - name + - "/branches/" + - branch + - "/changesets" - ) - .then(response => response.json()) - .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); - }) - .catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, branch, cause)); - }); - }; -} - -export function fetchChangesetsPending( - namespace: string, - name: string, - branch?: string -): Action { - return { - type: FETCH_CHANGESETS_PENDING, - payload: { - namespace, - name, - branch - }, - itemId: createItemId(namespace, name, branch) - }; -} - -export function fetchChangesetsSuccess( - collection: any, - namespace: string, - name: string, - branch?: string -): Action { - return { - type: FETCH_CHANGESETS_SUCCESS, - payload: { collection, namespace, name, branch }, - itemId: createItemId(namespace, name, branch) - }; -} - -function fetchChangesetsFailure( - namespace: string, - name: string, - branch?: string, - error: Error -): Action { - return { - type: FETCH_CHANGESETS_FAILURE, - payload: { - namespace, - name, - branch, - error - }, - itemId: createItemId(namespace, name, branch) - }; -} - -function createItemId( - namespace: string, - name: string, - branch?: string -): string { - let itemId = namespace + "/" + name; - if (branch && branch !== "") { - itemId = itemId + "/" + branch; - } - return itemId; -} - -// reducer -export default function reducer( - state: any = {}, - action: Action = { type: "UNKNOWN" } -): Object { - switch (action.type) { - case FETCH_CHANGESETS_SUCCESS: - const { namespace, name, branch } = action.payload; - const key = createItemId(namespace, name, branch); - let oldChangesets = { [key]: {} }; - if (state[key] !== undefined) { - oldChangesets[key] = state[key]; - } - return { - ...state, - [key]: { - byId: extractChangesetsByIds( - action.payload.collection, - oldChangesets[key].byId - ) - } - }; - default: - return state; - } -} - -function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { - const changesets = data._embedded.changesets; - const changesetsByIds = {}; - - for (let changeset of changesets) { - changesetsByIds[changeset.id] = changeset; - } - - for (let id in oldChangesetsByIds) { - changesetsByIds[id] = oldChangesetsByIds[id]; - } - - return changesetsByIds; -} - -//selectors -export function getChangesetsForNamespaceAndNameFromState( - namespace: string, - name: string, - state: Object -) { - const key = createItemId(namespace, name); - if (!state.changesets[key]) { - return null; - } - return Object.values(state.changesets[key].byId); -} - -export function getChangesets( - state: Object, - namespace: string, - name: string, - branch: string -) { - const key = createItemId(namespace, name, branch); - if (!state.changesets[key]) { - return null; - } - return Object.values(state.changesets[key].byId); -} - -export function isFetchChangesetsPending( - state: Object, - namespace: string, - name: string, - branch?: string -) { - return isPending( - state, - FETCH_CHANGESETS, - createItemId(namespace, name, branch) - ); -} - -export function getFetchChangesetsFailure( - state: Object, - namespace: string, - name: string, - branch?: string -) { - return getFailure( - state, - FETCH_CHANGESETS, - createItemId(namespace, name, branch) - ); -} diff --git a/scm-ui/src/repos/changesets/modules/changesets.js b/scm-ui/src/repos/changesets/modules/changesets.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/scm-ui/src/repos/changesets/modules/changesets.test.js b/scm-ui/src/repos/changesets/modules/changesets.test.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/scm-ui/src/repos/changesets/types/Changeset.js b/scm-ui/src/repos/changesets/types/Changeset.js deleted file mode 100644 index ac7b9861f7..0000000000 --- a/scm-ui/src/repos/changesets/types/Changeset.js +++ /dev/null @@ -1,13 +0,0 @@ -//@flow -import type { Links } from "@scm-manager/ui-types"; - -export type Changeset = { - id: String, - author: { - mail: String, - name: String - }, - date: String, - description: String, - _links: Links -}; diff --git a/scm-ui/src/repos/changesets/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js similarity index 100% rename from scm-ui/src/repos/changesets/containers/ChangesetView.js rename to scm-ui/src/repos/containers/ChangesetView.js diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 5d151e33dd..ebb891002c 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -92,7 +92,7 @@ class Changesets extends React.Component { ); } - return ; + return ; }; renderPaginator() { diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 3017f02000..a59dfb8258 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -8,7 +8,7 @@ import { isFetchRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import { Route, Switch } from "react-router-dom"; +import { Route } from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; import { Page, @@ -25,8 +25,8 @@ import Edit from "../containers/Edit"; import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; -import ChangesetView from "../changesets/containers/ChangesetView"; import Changesets from "./Changesets"; +import ChangesetView from "./ChangesetView"; type Props = { namespace: string, @@ -104,15 +104,18 @@ class RepositoryRoot extends React.Component { component={() => } /> } /> } /> } /> diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index b806fff535..a39bb407c2 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -16,9 +16,104 @@ export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; +//added for detailed view of changesets + +export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; +export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; +export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; +export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; + +// end of detailed view add + +// actions const REPO_URL = "repositories"; //TODO: Content type -// actions + +//added for detailed view of changesets + +function fetchChangesetIfNeeded( + state: Object, + namespace: string, + repoName: string, + id: string +) { + return function(dispatch) { + if (shouldFetchChangeset(state, namespace, repoName, id)) { + dispatch(fetchChangeset(url)); + } + }; +} + +export function shouldFetchChangeset( + state: Object, + namespace: string, + repoName: string, + id: string +) { + // decide if changeset should be fetched here + return true; +} + +function fetchChangeset(namespace: string, repoName: string, id: string) { + return function(dispatch) { + dispatch(fetchChangesetPending(namespace, repoName, id)); + return apiClient + .get(url) + .then(response => response.json()) + .then(json => dispatch(fetchChangesetSuccess(namespace, repoName, id))) + .catch(err => { + dispatch(fetchChangesetFailure(namespace, repoName, id, err)); + }); + }; +} + +export function fetchChangesetPending( + namespace: string, + repoName: string, + id: string +): Action { + return { + type: FETCH_CHANGESET_PENDING, + payload: { + namespace, + repoName, + id + }, + itemId: createItemId(namespace, repoName) + "/" + id + }; +} + +export function fetchChangesetSuccess( + namespace: string, + repoName: string, + id: string +): Action { + return { + type: FETCH_CHANGESET_SUCCESS, + payload: { namespace, repoName, id }, + itemId: createItemId(namespace, repoName) + "/" + id + }; +} + +function fetchChangesetFailure( + namespace: string, + name: string, + id: string, + error: Error +): Action { + return { + type: FETCH_CHANGESET_FAILURE, + payload: { + namespace, + name, + id, + error + }, + itemId: createItemId(namespace, repoName) + "/" + id + }; +} + +// end of detailed view add export function fetchChangesetsWithOptions( namespace: string, From a1838c1ec8ea9a5a4764d693ac332f2472d3f583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 12:03:49 +0200 Subject: [PATCH 11/96] add reducer, selector and actions for getting one changeset --- scm-ui/src/repos/modules/changesets.js | 138 +++++++-- scm-ui/src/repos/modules/changesets.test.js | 307 +++++++++++++++++++- 2 files changed, 420 insertions(+), 25 deletions(-) diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index a39bb407c2..dfe1a77f75 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -16,51 +16,47 @@ export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; -//added for detailed view of changesets +//********added for detailed view of changesets export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; -// end of detailed view add +//********end of detailed view add // actions const REPO_URL = "repositories"; //TODO: Content type -//added for detailed view of changesets +//********added for detailed view of changesets -function fetchChangesetIfNeeded( +export function fetchChangesetIfNeeded( state: Object, namespace: string, repoName: string, id: string ) { - return function(dispatch) { + return function(dispatch: any) { if (shouldFetchChangeset(state, namespace, repoName, id)) { - dispatch(fetchChangeset(url)); + return dispatch(fetchChangeset(namespace, repoName, id)); } }; } -export function shouldFetchChangeset( - state: Object, +export function fetchChangeset( namespace: string, repoName: string, id: string ) { - // decide if changeset should be fetched here - return true; -} - -function fetchChangeset(namespace: string, repoName: string, id: string) { - return function(dispatch) { + return function(dispatch: any) { dispatch(fetchChangesetPending(namespace, repoName, id)); return apiClient - .get(url) + .get(REPO_URL + `/${namespace}/${repoName}/changesets/${id}`) .then(response => response.json()) - .then(json => dispatch(fetchChangesetSuccess(namespace, repoName, id))) + .then(data => + dispatch(fetchChangesetSuccess(data, namespace, repoName, id)) + ) .catch(err => { dispatch(fetchChangesetFailure(namespace, repoName, id, err)); }); @@ -79,25 +75,26 @@ export function fetchChangesetPending( repoName, id }, - itemId: createItemId(namespace, repoName) + "/" + id + itemId: createItemId(namespace, repoName, id) }; } export function fetchChangesetSuccess( + changeset: any, namespace: string, repoName: string, id: string ): Action { return { type: FETCH_CHANGESET_SUCCESS, - payload: { namespace, repoName, id }, - itemId: createItemId(namespace, repoName) + "/" + id + payload: { changeset, namespace, repoName, id }, + itemId: createItemId(namespace, repoName, id) }; } function fetchChangesetFailure( namespace: string, - name: string, + repoName: string, id: string, error: Error ): Action { @@ -105,15 +102,14 @@ function fetchChangesetFailure( type: FETCH_CHANGESET_FAILURE, payload: { namespace, - name, + repoName, id, error }, - itemId: createItemId(namespace, repoName) + "/" + id + itemId: createItemId(namespace, repoName, id) }; } - -// end of detailed view add +//********end of detailed view add export function fetchChangesetsWithOptions( namespace: string, @@ -234,6 +230,26 @@ function byKeyReducer( action: Action = { type: "UNKNOWN" } ): Object { switch (action.type) { + //********added for detailed view of changesets + case FETCH_CHANGESET_SUCCESS: + const _key = createItemId( + action.payload.namespace, + action.payload.repoName + ); + let _oldChangesets = { [_key]: {} }; + if (state[_key] !== undefined) { + _oldChangesets[_key] = state[_key]; + } + return { + ...state, + [_key]: { + byId: addChangesetToChangesets( + action.payload.changeset, + _oldChangesets[_key].byId + ) + } + }; + //********end of added for detailed view of changesets case FETCH_CHANGESETS_SUCCESS: const key = action.itemId; let oldChangesets = { [key]: {} }; @@ -256,6 +272,18 @@ function listReducer( action: Action = { type: "UNKNOWN" } ): Object { switch (action.type) { + //********added for detailed view of changesets + case FETCH_CHANGESET_SUCCESS: + const changesetId = action.payload.changeset.id; + const stateEntries = state.entries; + stateEntries.push(changesetId); + return { + entries: stateEntries, + entry: { + ...state.entry + } + }; + //********end of added for detailed view of changesets case FETCH_CHANGESETS_SUCCESS: const changesets = action.payload._embedded.changesets; const changesetIds = changesets.map(c => c.id); @@ -291,6 +319,21 @@ function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { return changesetsByIds; } +//********added for detailed view of changesets + +function addChangesetToChangesets(data: any, oldChangesetsByIds: any) { + const changeset = data; + const changesetsByIds = {}; + + changesetsByIds[changeset.id] = changeset; + + for (let id in oldChangesetsByIds) { + changesetsByIds[id] = oldChangesetsByIds[id]; + } + + return changesetsByIds; +} +//********end of added for detailed view of changesets //selectors export function getChangesets( @@ -306,6 +349,53 @@ export function getChangesets( return Object.values(state.changesets.byKey[key].byId); } +//********added for detailed view of changesets +export function getChangeset( + state: Object, + namespace: string, + name: string, + id: string, + branch?: string +) { + const key = createItemId(namespace, name, branch); + const changesets = state.changesets.byKey[key].byId; + if (changesets != null && changesets[id]) { + return changesets[id]; + } + return null; +} + +export function shouldFetchChangeset( + state: Object, + namespace: string, + repoName: string, + id: string +) { + if (getChangeset(state, namespace, repoName, id)) { + return false; + } + return true; +} + +export function isFetchChangesetPending( + state: Object, + namespace: string, + name: string, + id: string +) { + return isPending(state, FETCH_CHANGESET, createItemId(namespace, name, id)); +} + +export function getFetchChangesetFailure( + state: Object, + namespace: string, + name: string, + id: string +) { + return getFailure(state, FETCH_CHANGESET, createItemId(namespace, name, id)); +} +//********end of added for detailed view of changesets + export function isFetchChangesetsPending( state: Object, namespace: string, diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index d46dff8ef7..ae5c9ce6ea 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -8,6 +8,10 @@ import { FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, + FETCH_CHANGESET, + FETCH_CHANGESET_FAILURE, + FETCH_CHANGESET_PENDING, + FETCH_CHANGESET_SUCCESS, fetchChangesets, fetchChangesetsByBranchAndPage, fetchChangesetsByNamespaceNameAndBranch, @@ -15,7 +19,14 @@ import { fetchChangesetsSuccess, getChangesets, getFetchChangesetsFailure, - isFetchChangesetsPending + isFetchChangesetsPending, + fetchChangeset, + getChangeset, + fetchChangesetIfNeeded, + shouldFetchChangeset, + isFetchChangesetPending, + getFetchChangesetFailure, + fetchChangesetSuccess } from "./changesets"; import reducer from "./changesets"; @@ -33,6 +44,139 @@ describe("changesets", () => { fetchMock.restore(); }); + //********added for detailed view of changesets + const changesetId = "aba876c0625d90a6aff1494f3d161aaa7008b958"; + + it("should fetch changeset", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, "{}"); + + const expectedActions = [ + { + type: FETCH_CHANGESET_PENDING, + payload: { + id: changesetId, + namespace: "foo", + repoName: "bar" + }, + itemId: "foo/bar/" + changesetId + }, + { + type: FETCH_CHANGESET_SUCCESS, + payload: { + changeset: {}, + id: changesetId, + namespace: "foo", + repoName: "bar" + }, + itemId: "foo/bar/" + changesetId + } + ]; + + const store = mockStore({}); + return store + .dispatch(fetchChangeset("foo", "bar", changesetId)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should fail fetching changeset on error", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, 500); + + const expectedActions = [ + { + type: FETCH_CHANGESET_PENDING, + payload: { + id: changesetId, + namespace: "foo", + repoName: "bar" + }, + itemId: "foo/bar/" + changesetId + } + ]; + + const store = mockStore({}); + return store + .dispatch(fetchChangeset("foo", "bar", changesetId)) + .then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESET_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); + }); + + it("should fetch changeset if needed", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + "id3", "{}"); + + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + + const expectedActions = [ + { + type: FETCH_CHANGESET_PENDING, + payload: { + id: "id3", + namespace: "foo", + repoName: "bar" + }, + itemId: "foo/bar/" + "id3" + }, + { + type: FETCH_CHANGESET_SUCCESS, + payload: { + changeset: {}, + id: "id3", + namespace: "foo", + repoName: "bar" + }, + itemId: "foo/bar/" + "id3" + } + ]; + + const store = mockStore({}); + return store + .dispatch(fetchChangesetIfNeeded(state, "foo", "bar", "id3")) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should not fetch changeset if not needed", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + "id1", 500); + + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + + const expectedActions = []; + + const store = mockStore({}); + return expect( + store.dispatch(fetchChangesetIfNeeded(state, "foo", "bar", "id1")) + ).toEqual(undefined); + }); + + //********end of added for detailed view of changesets + it("should fetch changesets for default branch", () => { fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}"); @@ -248,11 +392,172 @@ describe("changesets", () => { expect(newState.byKey["foo/bar"].byId["changeset2"]).toBeDefined(); expect(newState.byKey["foo/bar"].byId["changeset1"]).toBeDefined(); }); + + //********added for detailed view of changesets + const responseBodySingleChangeset = { + id: "id3", + author: { + mail: "z@phod.com", + name: "zaphod" + }, + date: "2018-09-13T08:46:22Z", + description: "added testChangeset", + _links: {}, + _embedded: { + tags: [], + branches: [] + } + }; + + it("should add changeset to state", () => { + const newState = reducer( + { + byKey: { + "foo/bar": { + byId: { + ["id2"]: { + id: "id2", + author: { mail: "mail@author.com", name: "author" } + } + } + } + }, + list: { + entry: { + page: 1, + pageTotal: 10, + _links: {} + }, + entries: ["id2"] + } + }, + fetchChangesetSuccess(responseBodySingleChangeset, "foo", "bar", "id3") + ); + expect(newState).toBeDefined(); + expect(newState.byKey["foo/bar"].byId["id3"].description).toEqual( + "added testChangeset" + ); + expect(newState.byKey["foo/bar"].byId["id3"].author.mail).toEqual( + "z@phod.com" + ); + expect(newState.byKey["foo/bar"].byId["id2"]).toBeDefined(); + expect(newState.byKey["foo/bar"].byId["id3"]).toBeDefined(); + expect(newState.list).toEqual({ + entry: { + page: 1, + pageTotal: 10, + _links: {} + }, + entries: ["id2", "id3"] + }); + }); + //********end of added for detailed view of changesets }); describe("changeset selectors", () => { const error = new Error("Something went wrong"); + //********added for detailed view of changesets + + it("should return changeset", () => { + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + const result = getChangeset(state, "foo", "bar", "id1"); + expect(result).toEqual({ id: "id1" }); + }); + + it("should return null if changeset does not exist", () => { + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + const result = getChangeset(state, "foo", "bar", "id3"); + expect(result).toEqual(null); + }); + + it("should return true if changeset does not exist", () => { + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + const result = shouldFetchChangeset(state, "foo", "bar", "id3"); + expect(result).toEqual(true); + }); + + it("should return false if changeset exists", () => { + const state = { + changesets: { + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } + } + } + } + }; + const result = shouldFetchChangeset(state, "foo", "bar", "id2"); + expect(result).toEqual(false); + }); + + it("should return true, when fetching changeset is pending", () => { + const state = { + pending: { + [FETCH_CHANGESET + "/foo/bar/id1"]: true + } + }; + + expect(isFetchChangesetPending(state, "foo", "bar", "id1")).toBeTruthy(); + }); + + it("should return false, when fetching changeset is not pending", () => { + expect(isFetchChangesetPending({}, "foo", "bar", "id1")).toEqual(false); + }); + + it("should return error if fetching changeset failed", () => { + const state = { + failure: { + [FETCH_CHANGESET + "/foo/bar/id1"]: error + } + }; + + expect(getFetchChangesetFailure(state, "foo", "bar", "id1")).toEqual( + error + ); + }); + + it("should return false if fetching changeset did not fail", () => { + expect(getFetchChangesetFailure({}, "foo", "bar", "id1")).toBeUndefined(); + }); + //********end of added for detailed view of changesets + it("should get all changesets for a given namespace and name", () => { const state = { changesets: { From e38275259f704032d1ab54a5c31be83d11ad14ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 20 Sep 2018 15:43:12 +0200 Subject: [PATCH 12/96] add fetchifneeded to changesets view and use state --- scm-ui/src/repos/containers/ChangesetView.js | 27 ++++++++++++++++++-- scm-ui/src/repos/modules/changesets.js | 5 ++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index e00b62aa14..c4042aab67 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -2,9 +2,20 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; +import type { Changeset } from "@scm-manager/ui-types"; +import { fetchChangesetIfNeeded } from "../modules/changesets"; type Props = { - id: string + id: string, + changeset: Changeset, + repository: Repository, + repositories: Repository[], + fetchChangesetIfNeeded: ( + state: Object, + namespace: string, + repoName: string, + id: string + ) => void }; class ChangesetView extends React.Component { @@ -14,7 +25,10 @@ class ChangesetView extends React.Component { } componentDidMount() { + const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; + //state macht keinen Sinn?! repositories holen! + fetchChangesetIfNeeded(repository.namespace, repository.name, id); } render() { @@ -29,7 +43,16 @@ const mapStateToProps = (state, ownProps: Props) => { }; const mapDispatchToProps = dispatch => { - return null; + return { + fetchChangesetIfNeeded: ( + state: Object, + namespace: string, + repoName: string, + id: string + ) => { + dispatch(fetchChangesetIfNeeded(state, namespace, repoName, id)); + } + }; }; export default withRouter( diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index dfe1a77f75..5b3555cc70 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -37,8 +37,9 @@ export function fetchChangesetIfNeeded( repoName: string, id: string ) { - return function(dispatch: any) { - if (shouldFetchChangeset(state, namespace, repoName, id)) { + return function(dispatch: any, getState) { + console.log(getState()); + if (shouldFetchChangeset(getState(), namespace, repoName, id)) { return dispatch(fetchChangeset(namespace, repoName, id)); } }; From e3c7f50797cdbe1c97e0c78719e41de855f8b1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 13:18:59 +0200 Subject: [PATCH 13/96] try to use getState --- scm-ui/src/repos/containers/ChangesetView.js | 2 +- scm-ui/src/repos/modules/changesets.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index c4042aab67..08a5be44fe 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -28,7 +28,7 @@ class ChangesetView extends React.Component { const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; //state macht keinen Sinn?! repositories holen! - fetchChangesetIfNeeded(repository.namespace, repository.name, id); + fetchChangesetIfNeeded(null, repository.namespace, repository.name, id); } render() { diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 5b3555cc70..ef9545c5a3 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -37,7 +37,7 @@ export function fetchChangesetIfNeeded( repoName: string, id: string ) { - return function(dispatch: any, getState) { + return (dispatch: any, getState: any) => { console.log(getState()); if (shouldFetchChangeset(getState(), namespace, repoName, id)) { return dispatch(fetchChangeset(namespace, repoName, id)); From 5463796741da3a895897cc96a882e1baee4e8441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 14:11:18 +0200 Subject: [PATCH 14/96] fetch changeset correct --- scm-ui/src/repos/containers/ChangesetView.js | 6 ++---- scm-ui/src/repos/modules/changesets.js | 10 ++++++---- scm-ui/src/repos/modules/changesets.test.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 08a5be44fe..729ca40020 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -11,7 +11,6 @@ type Props = { repository: Repository, repositories: Repository[], fetchChangesetIfNeeded: ( - state: Object, namespace: string, repoName: string, id: string @@ -28,7 +27,7 @@ class ChangesetView extends React.Component { const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; //state macht keinen Sinn?! repositories holen! - fetchChangesetIfNeeded(null, repository.namespace, repository.name, id); + fetchChangesetIfNeeded(repository.namespace, repository.name, id); } render() { @@ -45,12 +44,11 @@ const mapStateToProps = (state, ownProps: Props) => { const mapDispatchToProps = dispatch => { return { fetchChangesetIfNeeded: ( - state: Object, namespace: string, repoName: string, id: string ) => { - dispatch(fetchChangesetIfNeeded(state, namespace, repoName, id)); + dispatch(fetchChangesetIfNeeded(namespace, repoName, id)); } }; }; diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index ef9545c5a3..7e68e66f15 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -32,13 +32,11 @@ const REPO_URL = "repositories"; //********added for detailed view of changesets export function fetchChangesetIfNeeded( - state: Object, namespace: string, repoName: string, id: string ) { return (dispatch: any, getState: any) => { - console.log(getState()); if (shouldFetchChangeset(getState(), namespace, repoName, id)) { return dispatch(fetchChangeset(namespace, repoName, id)); } @@ -59,6 +57,7 @@ export function fetchChangeset( dispatch(fetchChangesetSuccess(data, namespace, repoName, id)) ) .catch(err => { + console.log(err); dispatch(fetchChangesetFailure(namespace, repoName, id, err)); }); }; @@ -276,7 +275,7 @@ function listReducer( //********added for detailed view of changesets case FETCH_CHANGESET_SUCCESS: const changesetId = action.payload.changeset.id; - const stateEntries = state.entries; + const stateEntries = state.entries ? state.entries : []; stateEntries.push(changesetId); return { entries: stateEntries, @@ -359,7 +358,10 @@ export function getChangeset( branch?: string ) { const key = createItemId(namespace, name, branch); - const changesets = state.changesets.byKey[key].byId; + const changesets = + state.changesets && state.changesets.byKey && state.changesets.byKey[key] + ? state.changesets.byKey[key].byId + : null; if (changesets != null && changesets[id]) { return changesets[id]; } diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index ae5c9ce6ea..65dd46cdbe 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -145,7 +145,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangesetIfNeeded(state, "foo", "bar", "id3")) + .dispatch(fetchChangesetIfNeeded("foo", "bar", "id3")) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -169,9 +169,9 @@ describe("changesets", () => { const expectedActions = []; - const store = mockStore({}); + const store = mockStore(state); return expect( - store.dispatch(fetchChangesetIfNeeded(state, "foo", "bar", "id1")) + store.dispatch(fetchChangesetIfNeeded("foo", "bar", "id1")) ).toEqual(undefined); }); From 9ee7fc7499c6c538362a7596d1c949c592f2facf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 15:08:57 +0200 Subject: [PATCH 15/96] complete module and show changesets, error and loading --- scm-ui/public/locales/en/changesets.json | 4 ++ .../src/repos/components/ChangesetDetails.js | 48 +++++++++++++++ scm-ui/src/repos/containers/ChangesetView.js | 58 ++++++++++++++----- scm-ui/src/repos/modules/changesets.js | 15 ++++- 4 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 scm-ui/src/repos/components/ChangesetDetails.js diff --git a/scm-ui/public/locales/en/changesets.json b/scm-ui/public/locales/en/changesets.json index b3e996e094..c1816db311 100644 --- a/scm-ui/public/locales/en/changesets.json +++ b/scm-ui/public/locales/en/changesets.json @@ -9,5 +9,9 @@ "author": { "name": "Author", "mail": "Mail" + }, + "changeset-error": { + "title": "Error", + "subtitle": "Unknown changeset error" } } diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js new file mode 100644 index 0000000000..b3084a7552 --- /dev/null +++ b/scm-ui/src/repos/components/ChangesetDetails.js @@ -0,0 +1,48 @@ +//@flow +import React from "react"; +import type { Changeset } from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; +import { MailLink, DateFromNow } from "@scm-manager/ui-components"; + +type Props = { + changeset: Changeset, + t: string => string +}; + +class ChangesetDetails extends React.Component { + render() { + const { changeset, t } = this.props; + return ( + + + + + + + + + + + + + + + + + + + + + + + +
{t("changeset.id")}{changeset.id}
{t("author.name")}{changeset.author.name}
{t("author.mail")} + +
{t("changeset.description")}{changeset.description}
{t("changeset.date")} + +
+ ); + } +} + +export default translate("changesets")(ChangesetDetails); diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 729ca40020..afc2f3d208 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -2,43 +2,68 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; -import type { Changeset } from "@scm-manager/ui-types"; -import { fetchChangesetIfNeeded } from "../modules/changesets"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; +import { + fetchChangesetIfNeeded, + fetchChangesetReset, + getChangeset, + getFetchChangesetFailure, + isFetchChangesetPending +} from "../modules/changesets"; +import ChangesetDetails from "../components/ChangesetDetails"; +import { translate } from "react-i18next"; +import { Loading, ErrorPage } from "@scm-manager/ui-components"; type Props = { id: string, changeset: Changeset, repository: Repository, - repositories: Repository[], + loading: boolean, + error: Error, fetchChangesetIfNeeded: ( namespace: string, repoName: string, id: string - ) => void + ) => void, + resetForm: (namespace: string, repoName: string, id: string) => void, + match: any, + t: string => string }; -class ChangesetView extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - +class ChangesetView extends React.Component { componentDidMount() { const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; - //state macht keinen Sinn?! repositories holen! fetchChangesetIfNeeded(repository.namespace, repository.name, id); + this.props.resetForm(repository.namespace, repository.name, id); } render() { - const id = this.props.match.params.id; + const { changeset, loading, error, t } = this.props; - return
Hallo! Changesets here! {id}
; + if (error) { + return ( + + ); + } + + if (!changeset || loading) return ; + + return ; } } const mapStateToProps = (state, ownProps: Props) => { - return null; + const { namespace, name } = ownProps.repository; + const id = ownProps.match.params.id; + const changeset = getChangeset(state, namespace, name, id); + const loading = isFetchChangesetPending(state, namespace, name, id); + const error = getFetchChangesetFailure(state, namespace, name, id); + return { changeset, error, loading }; }; const mapDispatchToProps = dispatch => { @@ -49,6 +74,9 @@ const mapDispatchToProps = dispatch => { id: string ) => { dispatch(fetchChangesetIfNeeded(namespace, repoName, id)); + }, + resetForm: (namespace: string, repoName: string, id: string) => { + dispatch(fetchChangesetReset(namespace, repoName, id)); } }; }; @@ -57,5 +85,5 @@ export default withRouter( connect( mapStateToProps, mapDispatchToProps - )(ChangesetView) + )(translate("changesets")(ChangesetView)) ); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 7e68e66f15..22755137da 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -10,6 +10,8 @@ import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; import type { Action, PagedCollection } from "@scm-manager/ui-types"; +import * as types from "../../modules/types"; +import { CREATE_USER_RESET } from "../../users/modules/users"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -22,6 +24,7 @@ export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; +export const FETCH_CHANGESET_RESET = `${FETCH_CHANGESET}_${types.RESET_SUFFIX}`; //********end of detailed view add @@ -57,7 +60,6 @@ export function fetchChangeset( dispatch(fetchChangesetSuccess(data, namespace, repoName, id)) ) .catch(err => { - console.log(err); dispatch(fetchChangesetFailure(namespace, repoName, id, err)); }); }; @@ -109,6 +111,17 @@ function fetchChangesetFailure( itemId: createItemId(namespace, repoName, id) }; } + +export function fetchChangesetReset( + namespace: string, + repoName: string, + id: string +) { + return { + type: FETCH_CHANGESET_RESET, + itemId: createItemId(namespace, repoName, id) + }; +} //********end of detailed view add export function fetchChangesetsWithOptions( From 24589556c7303df2335109167f0a0a0bbd15a91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 15:38:59 +0200 Subject: [PATCH 16/96] add extension point --- .../src/repos/components/ChangesetDetails.js | 62 ++++++++++--------- scm-ui/src/repos/modules/changesets.js | 1 - 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js index b3084a7552..8c055f7045 100644 --- a/scm-ui/src/repos/components/ChangesetDetails.js +++ b/scm-ui/src/repos/components/ChangesetDetails.js @@ -3,6 +3,7 @@ import React from "react"; import type { Changeset } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; import { MailLink, DateFromNow } from "@scm-manager/ui-components"; +import ChangesetAvatar from "./ChangesetAvatar"; type Props = { changeset: Changeset, @@ -13,34 +14,39 @@ class ChangesetDetails extends React.Component { render() { const { changeset, t } = this.props; return ( - - - - - - - - - - - - - - - - - - - - - - - -
{t("changeset.id")}{changeset.id}
{t("author.name")}{changeset.author.name}
{t("author.mail")} - -
{t("changeset.description")}{changeset.description}
{t("changeset.date")} - -
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
{t("changeset.id")}{changeset.id}
{t("author.name")}{changeset.author.name}
{t("author.mail")} + +
{t("changeset.description")}{changeset.description}
{t("changeset.date")} + +
+
); } } diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 22755137da..03ca2eea2c 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -11,7 +11,6 @@ import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; import type { Action, PagedCollection } from "@scm-manager/ui-types"; import * as types from "../../modules/types"; -import { CREATE_USER_RESET } from "../../users/modules/users"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; From ca725100bac961cfbd2d99e06f50c11bee9fea47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 16:12:03 +0200 Subject: [PATCH 17/96] avoid reloading of state --- scm-ui/src/repos/containers/RepositoryRoot.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index a59dfb8258..e8c9681caf 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -106,17 +106,17 @@ class RepositoryRoot extends React.Component { } + render={() => } /> } + render={() => } /> } + render={() => } />
From 34444ac4a92a7f8b8dff62fa16b944a8f4825789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 25 Sep 2018 16:12:11 +0200 Subject: [PATCH 18/96] put image left to table --- .../src/repos/components/ChangesetDetails.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js index 8c055f7045..21049cdc51 100644 --- a/scm-ui/src/repos/components/ChangesetDetails.js +++ b/scm-ui/src/repos/components/ChangesetDetails.js @@ -4,21 +4,30 @@ import type { Changeset } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; import { MailLink, DateFromNow } from "@scm-manager/ui-components"; import ChangesetAvatar from "./ChangesetAvatar"; +import classNames from "classnames"; +import injectSheet from "react-jss"; + +const styles = { + floatLeft: { + float: "left" + } +}; type Props = { changeset: Changeset, - t: string => string + t: string => string, + classes: any }; class ChangesetDetails extends React.Component { render() { - const { changeset, t } = this.props; + const { changeset, t, classes } = this.props; return (
-
+
- +
@@ -51,4 +60,4 @@ class ChangesetDetails extends React.Component { } } -export default translate("changesets")(ChangesetDetails); +export default injectSheet(styles)(translate("changesets")(ChangesetDetails)); From a1e0e790ad176ae9a9b8dfcbc7780988e6c2c114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 27 Sep 2018 09:59:25 +0200 Subject: [PATCH 19/96] allow navlink to be active at other links --- .../packages/ui-components/src/navigation/NavLink.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js index 3a90fd1fc6..1f12e65546 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js +++ b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js @@ -7,7 +7,8 @@ import { Route, Link } from "react-router-dom"; type Props = { to: string, label: string, - activeOnlyWhenExact?: boolean + activeOnlyWhenExact?: boolean, + otherLocation: (route: any) => boolean }; class NavLink extends React.Component { @@ -16,10 +17,10 @@ class NavLink extends React.Component { }; renderLink = (route: any) => { - const { to, label } = this.props; + const { to, label, otherLocation } = this.props; return (
  • - + {label}
  • From 2a684ec0a646420d4054a27df578006788fbcce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 27 Sep 2018 09:59:52 +0200 Subject: [PATCH 20/96] activate navLink of history if changesets are shown --- scm-ui/src/repos/containers/RepositoryRoot.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index e8c9681caf..b4a54ad168 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -71,6 +71,11 @@ class RepositoryRoot extends React.Component { this.props.deleteRepo(repository, this.deleted); }; + matchChangeset = (route: any) => { + const url = this.matchedUrl(); + return route.location.pathname.match(`${url}/changeset/`); + }; + render() { const { loading, error, repository, t } = this.props; @@ -127,6 +132,7 @@ class RepositoryRoot extends React.Component { activeOnlyWhenExact={false} to={`${url}/history`} label={t("repository-root.history")} + otherLocation={this.matchChangeset} /> From 991640bc4b7b7892309e70af07befb1ef8f06b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 27 Sep 2018 11:56:04 +0200 Subject: [PATCH 21/96] show no image if extension point is not used --- scm-ui/src/repos/components/ChangesetAvatar.js | 15 +++++++-------- scm-ui/src/repos/components/ChangesetDetails.js | 13 +++++++------ scm-ui/src/repos/containers/ChangesetView.js | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetAvatar.js b/scm-ui/src/repos/components/ChangesetAvatar.js index eb461d374d..70c7ecb2dd 100644 --- a/scm-ui/src/repos/components/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/ChangesetAvatar.js @@ -1,24 +1,23 @@ //@flow import React from "react"; -import {ExtensionPoint} from "@scm-manager/ui-extensions"; -import type {Changeset} from "@scm-manager/ui-types"; -import {Image} from "@scm-manager/ui-components"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; +import { Image } from "@scm-manager/ui-components"; type Props = { - changeset: Changeset + changeset: Changeset, + repository: Repository }; class ChangesetAvatar extends React.Component { render() { - const { changeset } = this.props; + const { changeset, repository } = this.props; return (

    -

    ); diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js index 21049cdc51..c9bc615866 100644 --- a/scm-ui/src/repos/components/ChangesetDetails.js +++ b/scm-ui/src/repos/components/ChangesetDetails.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import type { Changeset } from "@scm-manager/ui-types"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; import { MailLink, DateFromNow } from "@scm-manager/ui-components"; import ChangesetAvatar from "./ChangesetAvatar"; @@ -15,19 +15,17 @@ const styles = { type Props = { changeset: Changeset, + repository: Repository, t: string => string, classes: any }; class ChangesetDetails extends React.Component { render() { - const { changeset, t, classes } = this.props; + const { changeset, repository, t, classes } = this.props; return (
    -
    - -
    -
    {t("changeset.id")}
    +
    @@ -55,6 +53,9 @@ class ChangesetDetails extends React.Component {
    {t("changeset.id")}
    +
    + +
    ); } diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index afc2f3d208..01ea64ed1b 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -39,7 +39,7 @@ class ChangesetView extends React.Component { } render() { - const { changeset, loading, error, t } = this.props; + const { changeset, loading, error, t, repository } = this.props; if (error) { return ( @@ -53,7 +53,7 @@ class ChangesetView extends React.Component { if (!changeset || loading) return ; - return ; + return ; } } From 33ffd4baa8158f7618dd0c9c46088784a1d66adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 27 Sep 2018 11:57:19 +0200 Subject: [PATCH 22/96] =?UTF-8?q?remove=20reset=20state=20since=20it=20is?= =?UTF-8?q?=20not=20needed2=20=C2=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scm-ui/src/repos/containers/ChangesetView.js | 1 - scm-ui/src/repos/modules/changesets.js | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 01ea64ed1b..78cc35a6e7 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -35,7 +35,6 @@ class ChangesetView extends React.Component { const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; fetchChangesetIfNeeded(repository.namespace, repository.name, id); - this.props.resetForm(repository.namespace, repository.name, id); } render() { diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 03ca2eea2c..b38185599a 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -23,7 +23,6 @@ export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; -export const FETCH_CHANGESET_RESET = `${FETCH_CHANGESET}_${types.RESET_SUFFIX}`; //********end of detailed view add @@ -111,16 +110,6 @@ function fetchChangesetFailure( }; } -export function fetchChangesetReset( - namespace: string, - repoName: string, - id: string -) { - return { - type: FETCH_CHANGESET_RESET, - itemId: createItemId(namespace, repoName, id) - }; -} //********end of detailed view add export function fetchChangesetsWithOptions( From 700abf7fe0d719e2e99082bc9a32a2a3a3294f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 27 Sep 2018 11:59:10 +0200 Subject: [PATCH 23/96] change name of extension point --- scm-ui/src/repos/components/ChangesetAvatar.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetAvatar.js b/scm-ui/src/repos/components/ChangesetAvatar.js index 70c7ecb2dd..a23b58aed9 100644 --- a/scm-ui/src/repos/components/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/ChangesetAvatar.js @@ -15,10 +15,9 @@ class ChangesetAvatar extends React.Component { return (

    - + />

    ); } From b0110563521a881cc0c029b3b83b0cdea012d053 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 27 Sep 2018 14:30:54 +0200 Subject: [PATCH 24/96] create new branch feature/ui-sources From 2b7453fc57857956d692e39bb79394b04a431b5a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 27 Sep 2018 16:32:37 +0200 Subject: [PATCH 25/96] implemented ui for sources root --- .../packages/ui-types/src/Sources.js | 27 +++ .../packages/ui-types/src/index.js | 2 + scm-ui/public/locales/en/repos.json | 11 +- scm-ui/src/createReduxStore.js | 4 +- .../src/repos/components/RepositoryNavLink.js | 28 +++ .../components/RepositoryNavLink.test.js | 49 +++++ scm-ui/src/repos/containers/RepositoryRoot.js | 12 ++ .../src/repos/sources/components/FileIcon.js | 20 ++ .../src/repos/sources/components/FileSize.js | 27 +++ .../repos/sources/components/FileSize.test.js | 9 + .../src/repos/sources/components/FileTree.js | 48 +++++ .../repos/sources/components/FileTreeLeaf.js | 49 +++++ .../src/repos/sources/containers/Sources.js | 72 +++++++ scm-ui/src/repos/sources/modules/sources.js | 106 +++++++++++ .../src/repos/sources/modules/sources.test.js | 178 ++++++++++++++++++ 15 files changed, 640 insertions(+), 2 deletions(-) create mode 100644 scm-ui-components/packages/ui-types/src/Sources.js create mode 100644 scm-ui/src/repos/components/RepositoryNavLink.js create mode 100644 scm-ui/src/repos/components/RepositoryNavLink.test.js create mode 100644 scm-ui/src/repos/sources/components/FileIcon.js create mode 100644 scm-ui/src/repos/sources/components/FileSize.js create mode 100644 scm-ui/src/repos/sources/components/FileSize.test.js create mode 100644 scm-ui/src/repos/sources/components/FileTree.js create mode 100644 scm-ui/src/repos/sources/components/FileTreeLeaf.js create mode 100644 scm-ui/src/repos/sources/containers/Sources.js create mode 100644 scm-ui/src/repos/sources/modules/sources.js create mode 100644 scm-ui/src/repos/sources/modules/sources.test.js diff --git a/scm-ui-components/packages/ui-types/src/Sources.js b/scm-ui-components/packages/ui-types/src/Sources.js new file mode 100644 index 0000000000..0aead5d05c --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/Sources.js @@ -0,0 +1,27 @@ +// @flow + +import type { Collection, Links } from "./hal"; + +// TODO ?? check ?? links +export type SubRepository = { + repositoryUrl: string, + browserUrl: string, + revision: string +}; + +export type File = { + name: string, + path: string, + directory: boolean, + description?: string, + length: number, + lastModified?: string, + subRepository?: SubRepository, // TODO + _links: Links +}; + +export type SourcesCollection = Collection & { + _embedded: { + files: File[] + } +}; diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index 109fb9bb6a..60a259f0f2 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -10,3 +10,5 @@ export type { Repository, RepositoryCollection, RepositoryGroup } from "./Reposi export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes"; export type { Config } from "./Config"; + +export type { SubRepository, File, SourcesCollection } from "./Sources"; diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 7db2623247..b8bbe19c4f 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -22,7 +22,8 @@ "actions-label": "Actions", "back-label": "Back", "navigation-label": "Navigation", - "information": "Information" + "information": "Information", + "sources": "Sources" }, "create": { "title": "Create Repository", @@ -42,5 +43,13 @@ "submit": "Yes", "cancel": "No" } + }, + "sources": { + "file-tree": { + "name": "Name", + "length": "Length", + "lastModified": "Last modified", + "description": "Description" + } } } diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 8411326b53..b2e09dd65b 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -7,6 +7,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux"; import users from "./users/modules/users"; import repos from "./repos/modules/repos"; import repositoryTypes from "./repos/modules/repositoryTypes"; +import sources from "./repos/sources/modules/sources"; import groups from "./groups/modules/groups"; import auth from "./modules/auth"; import pending from "./modules/pending"; @@ -28,7 +29,8 @@ function createReduxStore(history: BrowserHistory) { repositoryTypes, groups, auth, - config + config, + sources }); return createStore( diff --git a/scm-ui/src/repos/components/RepositoryNavLink.js b/scm-ui/src/repos/components/RepositoryNavLink.js new file mode 100644 index 0000000000..8497e207cd --- /dev/null +++ b/scm-ui/src/repos/components/RepositoryNavLink.js @@ -0,0 +1,28 @@ +//@flow +import React from "react"; +import type { Repository } from "@scm-manager/ui-types"; +import { NavLink } from "@scm-manager/ui-components"; + +type Props = { + repository: Repository, + to: string, + label: string, + linkName: string +}; + +/** + * Component renders only if the repository contains the link with the given name. + */ +class RepositoryNavLink extends React.Component { + render() { + const { linkName, to, label, repository } = this.props; + + if (!repository._links[linkName]) { + return null; + } + + return ; + } +} + +export default RepositoryNavLink; diff --git a/scm-ui/src/repos/components/RepositoryNavLink.test.js b/scm-ui/src/repos/components/RepositoryNavLink.test.js new file mode 100644 index 0000000000..0d93cb7c4d --- /dev/null +++ b/scm-ui/src/repos/components/RepositoryNavLink.test.js @@ -0,0 +1,49 @@ +// @flow +import React from "react"; +import { shallow, mount } from "enzyme"; +import "../../tests/enzyme"; +import "../../tests/i18n"; +import ReactRouterEnzymeContext from "react-router-enzyme-context"; +import RepositoryNavLink from "./RepositoryNavLink"; + +describe("RepositoryNavLink", () => { + const options = new ReactRouterEnzymeContext(); + + it("should render nothing, if the sources link is missing", () => { + const repository = { + _links: {} + }; + + const navLink = shallow( + , + options.get() + ); + expect(navLink.text()).toBe(""); + }); + + it("should render the navLink", () => { + const repository = { + _links: { + sources: { + href: "/sources" + } + } + }; + + const navLink = mount( + , + options.get() + ); + expect(navLink.text()).toBe("Sources"); + }); +}); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 2816cdb5c5..78041764c7 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -25,6 +25,8 @@ import Edit from "../containers/Edit"; import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; +import Sources from "../sources/containers/Sources"; +import RepositoryNavLink from "../components/RepositoryNavLink"; type Props = { namespace: string, @@ -101,12 +103,22 @@ class RepositoryRoot extends React.Component { path={`${url}/edit`} component={() => } /> + } + />
    +
    diff --git a/scm-ui/src/repos/sources/components/FileIcon.js b/scm-ui/src/repos/sources/components/FileIcon.js new file mode 100644 index 0000000000..4615c0cddc --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileIcon.js @@ -0,0 +1,20 @@ +// @flow +import React from "react"; +import type { File } from "@scm-manager/ui-types"; + +type Props = { + file: File +}; + +class FileIcon extends React.Component { + render() { + const { file } = this.props; + let icon = "file"; + if (file.directory) { + icon = "folder"; + } + return ; + } +} + +export default FileIcon; diff --git a/scm-ui/src/repos/sources/components/FileSize.js b/scm-ui/src/repos/sources/components/FileSize.js new file mode 100644 index 0000000000..1f613ad9fc --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileSize.js @@ -0,0 +1,27 @@ +// @flow +import React from "react"; + +type Props = { + bytes: number +}; + +class FileSize extends React.Component { + static format(bytes) { + if (!bytes) { + return ""; + } + + const units = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + + const size = (bytes / 1024 ** i).toFixed(2); + return `${size} ${units[i]}`; + } + + render() { + const formattedBytes = FileSize.format(this.props.bytes); + return {formattedBytes}; + } +} + +export default FileSize; diff --git a/scm-ui/src/repos/sources/components/FileSize.test.js b/scm-ui/src/repos/sources/components/FileSize.test.js new file mode 100644 index 0000000000..1bbe511eb1 --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileSize.test.js @@ -0,0 +1,9 @@ +import FileSize from "./FileSize"; + +it("should format bytes", () => { + expect(FileSize.format(160)).toBe("160.00 B"); + expect(FileSize.format(6304)).toBe("6.16 K"); + expect(FileSize.format(28792588)).toBe("27.46 M"); + expect(FileSize.format(1369510189)).toBe("1.28 G"); + expect(FileSize.format(42949672960)).toBe("40.00 G"); +}); diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js new file mode 100644 index 0000000000..5afb6309ee --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -0,0 +1,48 @@ +//@flow +import React from "react"; +import { translate } from "react-i18next"; +import injectSheet from "react-jss"; +import FileTreeLeaf from "./FileTreeLeaf"; +import type { SourcesCollection } from "@scm-manager/ui-types"; + +const styles = { + iconColumn: { + width: "16px" + } +}; + +type Props = { + tree: SourcesCollection, + + // context props + classes: any, + t: string => string +}; + +class FileTree extends React.Component { + render() { + const { tree, classes, t } = this.props; + + const files = tree._embedded.files; + + return ( + + + + + + + + + + + {files.map(file => ( + + ))} + +
    + {t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.lastModified")}{t("sources.file-tree.description")}
    + ); + } +} +export default injectSheet(styles)(translate("repos")(FileTree)); diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js new file mode 100644 index 0000000000..440a87c8e3 --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -0,0 +1,49 @@ +//@flow +import React from "react"; +import injectSheet from "react-jss"; +import { DateFromNow } from "@scm-manager/ui-components"; +import FileSize from "./FileSize"; +import FileIcon from "./FileIcon"; +import { Link } from "react-router-dom"; +import type { File } from "@scm-manager/ui-types"; + +const styles = { + iconColumn: { + width: "16px" + } +}; + +type Props = { + file: File, + + // context props + classes: any +}; + +class FileTreeLeaf extends React.Component { + render() { + const { file, classes } = this.props; + + return ( + + + + + + + + {file.name} + + + + + + + + {file.description} + + ); + } +} + +export default injectSheet(styles)(FileTreeLeaf); diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js new file mode 100644 index 0000000000..c7903e7ef3 --- /dev/null +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -0,0 +1,72 @@ +// @flow +import React from "react"; +import { connect } from "react-redux"; +import type { Repository, SourcesCollection } from "@scm-manager/ui-types"; +import FileTree from "../components/FileTree"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { + fetchSources, + getFetchSourcesFailure, + getSources, + isFetchSourcesPending +} from "../modules/sources"; + +type Props = { + repository: Repository, + sources: SourcesCollection, + loading: boolean, + error: Error, + + // dispatch props + fetchSources: (repository: Repository) => void +}; + +class Sources extends React.Component { + componentDidMount() { + const { fetchSources, repository } = this.props; + + fetchSources(repository); + } + + render() { + const { sources, loading, error } = this.props; + + if (error) { + return ; + } + + if (!sources || loading) { + return ; + } + + return ; + } +} + +const mapStateToProps = (state, ownProps) => { + const { repository } = ownProps; + const loading = isFetchSourcesPending(state, repository); + const error = getFetchSourcesFailure(state, repository); + const sources = getSources(state, repository); + + console.log(sources); + + return { + loading, + error, + sources + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchSources: (repository: Repository) => { + dispatch(fetchSources(repository)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Sources); diff --git a/scm-ui/src/repos/sources/modules/sources.js b/scm-ui/src/repos/sources/modules/sources.js new file mode 100644 index 0000000000..74bb25b80a --- /dev/null +++ b/scm-ui/src/repos/sources/modules/sources.js @@ -0,0 +1,106 @@ +// @flow + +import * as types from "../../../modules/types"; +import type { + Repository, + SourcesCollection, + Action +} from "@scm-manager/ui-types"; +import { apiClient } from "@scm-manager/ui-components"; +import { isPending } from "../../../modules/pending"; +import { getFailure } from "../../../modules/failure"; + +export const FETCH_SOURCES = "scm/repos/FETCH_SOURCES"; +export const FETCH_SOURCES_PENDING = `${FETCH_SOURCES}_${types.PENDING_SUFFIX}`; +export const FETCH_SOURCES_SUCCESS = `${FETCH_SOURCES}_${types.SUCCESS_SUFFIX}`; +export const FETCH_SOURCES_FAILURE = `${FETCH_SOURCES}_${types.FAILURE_SUFFIX}`; + +export function fetchSources(repository: Repository) { + return function(dispatch: any) { + dispatch(fetchSourcesPending(repository)); + return apiClient + .get(repository._links.sources.href) + .then(response => response.json()) + .then(sources => { + dispatch(fetchSourcesSuccess(repository, sources)); + }) + .catch(err => { + const error = new Error(`failed to fetch sources: ${err.message}`); + dispatch(fetchSourcesFailure(repository, error)); + }); + }; +} + +export function fetchSourcesPending(repository: Repository): Action { + return { + type: FETCH_SOURCES_PENDING, + itemId: createItemId(repository) + }; +} + +export function fetchSourcesSuccess( + repository: Repository, + sources: SourcesCollection +) { + return { + type: FETCH_SOURCES_SUCCESS, + payload: sources, + itemId: createItemId(repository) + }; +} + +export function fetchSourcesFailure( + repository: Repository, + error: Error +): Action { + return { + type: FETCH_SOURCES_FAILURE, + payload: error, + itemId: createItemId(repository) + }; +} + +function createItemId(repository: Repository) { + return `${repository.namespace}/${repository.name}`; +} + +// reducer + +export default function reducer( + state: any = {}, + action: Action = { type: "UNKNOWN" } +): any { + if (action.type === FETCH_SOURCES_SUCCESS) { + return { + [action.itemId]: action.payload, + ...state + }; + } + return state; +} + +// selectors + +export function getSources( + state: any, + repository: Repository +): ?SourcesCollection { + if (state.sources) { + return state.sources[createItemId(repository)]; + } + return null; +} + +export function isFetchSourcesPending( + state: any, + repository: Repository +): boolean { + return isPending(state, FETCH_SOURCES, createItemId(repository)); +} + +export function getFetchSourcesFailure( + state: any, + repository: Repository +): ?Error { + return getFailure(state, FETCH_SOURCES, createItemId(repository)); +} diff --git a/scm-ui/src/repos/sources/modules/sources.test.js b/scm-ui/src/repos/sources/modules/sources.test.js new file mode 100644 index 0000000000..3870d638e3 --- /dev/null +++ b/scm-ui/src/repos/sources/modules/sources.test.js @@ -0,0 +1,178 @@ +// @flow + +import type { Repository } from "@scm-manager/ui-types"; +import configureMockStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import fetchMock from "fetch-mock"; +import { + FETCH_SOURCES, + FETCH_SOURCES_FAILURE, + FETCH_SOURCES_PENDING, + FETCH_SOURCES_SUCCESS, + fetchSources, + getFetchSourcesFailure, + isFetchSourcesPending, + default as reducer, + getSources +} from "./sources"; + +const sourcesUrl = + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources"; + +const repository: Repository = { + name: "core", + namespace: "scm", + type: "git", + _links: { + sources: { + href: sourcesUrl + } + } +}; + +const collection = { + revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4", + _links: { + self: { + href: + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/" + } + }, + _embedded: { + files: [ + { + name: "src", + path: "src", + directory: true, + description: null, + length: 176, + lastModified: null, + subRepository: null, + _links: { + self: { + href: + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src" + } + } + }, + { + name: "package.json", + path: "package.json", + directory: false, + description: "bump version", + length: 780, + lastModified: "2017-07-31T11:17:19Z", + subRepository: null, + _links: { + self: { + href: + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/content/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json" + }, + history: { + href: + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/history/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json" + } + } + } + ] + } +}; + +describe("sources fetch", () => { + const mockStore = configureMockStore([thunk]); + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should fetch the sources of the repository", () => { + fetchMock.getOnce(sourcesUrl, collection); + + const expectedActions = [ + { type: FETCH_SOURCES_PENDING, itemId: "scm/core" }, + { + type: FETCH_SOURCES_SUCCESS, + itemId: "scm/core", + payload: collection + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchSources(repository)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should dispatch FETCH_SOURCES_FAILURE on server error", () => { + fetchMock.getOnce(sourcesUrl, { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(fetchSources(repository)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toBe(FETCH_SOURCES_PENDING); + expect(actions[1].type).toBe(FETCH_SOURCES_FAILURE); + expect(actions[1].itemId).toBe("scm/core"); + expect(actions[1].payload).toBeDefined(); + }); + }); +}); + +describe("reducer tests", () => { + it("should return unmodified state on unknown action", () => { + const state = {}; + expect(reducer(state)).toBe(state); + }); + + it("should store the collection", () => { + const expectedState = { + "scm/core": repository + }; + expect(reducer({}, fetchSources(repository))).toEqual(expectedState); + }); +}); + +describe("selector tests", () => { + it("should return null", () => { + expect(getSources({}, repository)).toBeFalsy(); + }); + + it("should return the source collection", () => { + const state = { + sources: { + "scm/core": collection + } + }; + expect(getSources(state, repository)).toBe(collection); + }); + + it("should return true, when fetch sources is pending", () => { + const state = { + pending: { + [FETCH_SOURCES + "/scm/core"]: true + } + }; + expect(isFetchSourcesPending(state, repository)).toEqual(true); + }); + + it("should return false, when fetch sources is not pending", () => { + expect(isFetchSourcesPending({}, repository)).toEqual(false); + }); + + const error = new Error("incredible error from hell"); + + it("should return error when fetch sources did fail", () => { + const state = { + failure: { + [FETCH_SOURCES + "/scm/core"]: error + } + }; + expect(getFetchSourcesFailure(state, repository)).toEqual(error); + }); + + it("should return undefined when fetch sources did not fail", () => { + expect(getFetchSourcesFailure({}, repository)).toBe(undefined); + }); +}); From 9606a8af2984feeff79bfb960c5bb3bbd35a6d41 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Sep 2018 08:14:37 +0200 Subject: [PATCH 26/96] fixed failing reducer test --- scm-ui/src/repos/sources/modules/sources.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/sources/modules/sources.test.js b/scm-ui/src/repos/sources/modules/sources.test.js index 3870d638e3..e047aaeb34 100644 --- a/scm-ui/src/repos/sources/modules/sources.test.js +++ b/scm-ui/src/repos/sources/modules/sources.test.js @@ -13,7 +13,8 @@ import { getFetchSourcesFailure, isFetchSourcesPending, default as reducer, - getSources + getSources, + fetchSourcesSuccess } from "./sources"; const sourcesUrl = @@ -128,9 +129,11 @@ describe("reducer tests", () => { it("should store the collection", () => { const expectedState = { - "scm/core": repository + "scm/core": collection }; - expect(reducer({}, fetchSources(repository))).toEqual(expectedState); + expect(reducer({}, fetchSourcesSuccess(repository, collection))).toEqual( + expectedState + ); }); }); From d1a9a1c63a40ae47aed618513a88426abb1b6243 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Sep 2018 11:31:38 +0200 Subject: [PATCH 27/96] implemented navigation within source browser --- .../packages/ui-types/src/Sources.js | 1 + .../src/repos/components/RepositoryNavLink.js | 13 +++- scm-ui/src/repos/containers/RepositoryRoot.js | 22 +++++- .../src/repos/sources/components/FileTree.js | 33 ++++++++- .../repos/sources/components/FileTree.test.js | 12 +++ .../repos/sources/components/FileTreeLeaf.js | 39 +++++++--- .../src/repos/sources/containers/Sources.js | 34 ++++++--- scm-ui/src/repos/sources/modules/sources.js | 73 ++++++++++++++----- .../src/repos/sources/modules/sources.test.js | 65 +++++++++++++---- 9 files changed, 233 insertions(+), 59 deletions(-) create mode 100644 scm-ui/src/repos/sources/components/FileTree.test.js diff --git a/scm-ui-components/packages/ui-types/src/Sources.js b/scm-ui-components/packages/ui-types/src/Sources.js index 0aead5d05c..0267212987 100644 --- a/scm-ui-components/packages/ui-types/src/Sources.js +++ b/scm-ui-components/packages/ui-types/src/Sources.js @@ -21,6 +21,7 @@ export type File = { }; export type SourcesCollection = Collection & { + revision: string, _embedded: { files: File[] } diff --git a/scm-ui/src/repos/components/RepositoryNavLink.js b/scm-ui/src/repos/components/RepositoryNavLink.js index 8497e207cd..4e315bcf9f 100644 --- a/scm-ui/src/repos/components/RepositoryNavLink.js +++ b/scm-ui/src/repos/components/RepositoryNavLink.js @@ -7,7 +7,8 @@ type Props = { repository: Repository, to: string, label: string, - linkName: string + linkName: string, + activeOnlyWhenExact: boolean }; /** @@ -15,13 +16,19 @@ type Props = { */ class RepositoryNavLink extends React.Component { render() { - const { linkName, to, label, repository } = this.props; + const { linkName, to, label, repository, activeOnlyWhenExact } = this.props; if (!repository._links[linkName]) { return null; } - return ; + return ( + + ); } } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 78041764c7..761ae0e2a4 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -8,7 +8,7 @@ import { isFetchRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import { Route } from "react-router-dom"; +import { Route, withRouter } from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; import { Page, @@ -105,7 +105,24 @@ class RepositoryRoot extends React.Component { /> } + exact={true} + component={props => ( + + )} + /> + ( + + )} />
    @@ -118,6 +135,7 @@ class RepositoryRoot extends React.Component { linkName="sources" to={`${url}/sources`} label={t("repository-root.sources")} + activeOnlyWhenExact={false} />
    diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 5afb6309ee..05a775c77a 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -13,17 +13,40 @@ const styles = { type Props = { tree: SourcesCollection, + path: string, + baseUrl: string, // context props classes: any, t: string => string }; +export function findParent(path: string) { + if (path.endsWith("/")) { + path = path.substring(path, path.length - 1); + } + + const index = path.lastIndexOf("/"); + if (index > 0) { + return path.substring(0, index); + } + return ""; +} + class FileTree extends React.Component { render() { - const { tree, classes, t } = this.props; + const { tree, path, baseUrl, classes, t } = this.props; + const baseUrlWithRevision = baseUrl + "/" + tree.revision; - const files = tree._embedded.files; + const files = []; + if (path) { + files.push({ + name: "..", + path: findParent(path), + directory: true + }); + } + files.push(...tree._embedded.files); return ( @@ -38,7 +61,11 @@ class FileTree extends React.Component { {files.map(file => ( - + ))}
    diff --git a/scm-ui/src/repos/sources/components/FileTree.test.js b/scm-ui/src/repos/sources/components/FileTree.test.js new file mode 100644 index 0000000000..77977df5bc --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileTree.test.js @@ -0,0 +1,12 @@ +// @flow + +import { findParent } from "./FileTree"; + +describe("find parent tests", () => { + it("should return the parent path", () => { + expect(findParent("src/main/js/")).toBe("src/main"); + expect(findParent("src/main/js")).toBe("src/main"); + expect(findParent("src/main")).toBe("src"); + expect(findParent("src")).toBe(""); + }); +}); diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 440a87c8e3..29d7d35ebe 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -1,5 +1,5 @@ //@flow -import React from "react"; +import * as React from "react"; import injectSheet from "react-jss"; import { DateFromNow } from "@scm-manager/ui-components"; import FileSize from "./FileSize"; @@ -15,25 +15,46 @@ const styles = { type Props = { file: File, + baseUrl: string, // context props classes: any }; class FileTreeLeaf extends React.Component { + createLink = (file: File) => { + let link = this.props.baseUrl; + if (file.path) { + link += "/" + file.path + "/"; + } + return link; + }; + + createFileIcon = (file: File) => { + if (file.directory) { + return ( + + + + ); + } + return ; + }; + + createFileName = (file: File) => { + if (file.directory) { + return {file.name}; + } + return file.name; + }; + render() { const { file, classes } = this.props; return ( - - - - - - - {file.name} - + {this.createFileIcon(file)} + {this.createFileName(file)} diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index c7903e7ef3..12a1a29759 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -16,20 +16,28 @@ type Props = { sources: SourcesCollection, loading: boolean, error: Error, + revision: string, + path: string, + baseUrl: string, // dispatch props - fetchSources: (repository: Repository) => void + fetchSources: ( + repository: Repository, + revision: string, + path: string + ) => void, + match: any }; class Sources extends React.Component { componentDidMount() { - const { fetchSources, repository } = this.props; + const { fetchSources, repository, revision, path } = this.props; - fetchSources(repository); + fetchSources(repository, revision, path); } render() { - const { sources, loading, error } = this.props; + const { sources, path, baseUrl, loading, error } = this.props; if (error) { return ; @@ -39,29 +47,31 @@ class Sources extends React.Component { return ; } - return ; + return ; } } const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; - const loading = isFetchSourcesPending(state, repository); - const error = getFetchSourcesFailure(state, repository); - const sources = getSources(state, repository); + const { revision, path } = ownProps.match.params; - console.log(sources); + const loading = isFetchSourcesPending(state, repository, revision, path); + const error = getFetchSourcesFailure(state, repository, revision, path); + const sources = getSources(state, repository, revision, path); return { loading, error, - sources + sources, + revision, + path }; }; const mapDispatchToProps = dispatch => { return { - fetchSources: (repository: Repository) => { - dispatch(fetchSources(repository)); + fetchSources: (repository: Repository, revision: string, path: string) => { + dispatch(fetchSources(repository, revision, path)); } }; }; diff --git a/scm-ui/src/repos/sources/modules/sources.js b/scm-ui/src/repos/sources/modules/sources.js index 74bb25b80a..7e4d21722e 100644 --- a/scm-ui/src/repos/sources/modules/sources.js +++ b/scm-ui/src/repos/sources/modules/sources.js @@ -15,53 +15,78 @@ export const FETCH_SOURCES_PENDING = `${FETCH_SOURCES}_${types.PENDING_SUFFIX}`; export const FETCH_SOURCES_SUCCESS = `${FETCH_SOURCES}_${types.SUCCESS_SUFFIX}`; export const FETCH_SOURCES_FAILURE = `${FETCH_SOURCES}_${types.FAILURE_SUFFIX}`; -export function fetchSources(repository: Repository) { +export function fetchSources( + repository: Repository, + revision: string, + path: string +) { return function(dispatch: any) { - dispatch(fetchSourcesPending(repository)); + dispatch(fetchSourcesPending(repository, revision, path)); return apiClient - .get(repository._links.sources.href) + .get(createUrl(repository, revision, path)) .then(response => response.json()) .then(sources => { - dispatch(fetchSourcesSuccess(repository, sources)); + dispatch(fetchSourcesSuccess(repository, revision, path, sources)); }) .catch(err => { const error = new Error(`failed to fetch sources: ${err.message}`); - dispatch(fetchSourcesFailure(repository, error)); + dispatch(fetchSourcesFailure(repository, revision, path, error)); }); }; } -export function fetchSourcesPending(repository: Repository): Action { +function createUrl(repository: Repository, revision: string, path: string) { + const base = repository._links.sources.href; + if (!revision && !path) { + return base; + } + + // TODO handle trailing slash + const pathDefined = path ? path : ""; + return `${base}${revision}/${pathDefined}`; +} + +export function fetchSourcesPending( + repository: Repository, + revision: string, + path: string +): Action { return { type: FETCH_SOURCES_PENDING, - itemId: createItemId(repository) + itemId: createItemId(repository, revision, path) }; } export function fetchSourcesSuccess( repository: Repository, + revision: string, + path: string, sources: SourcesCollection ) { return { type: FETCH_SOURCES_SUCCESS, payload: sources, - itemId: createItemId(repository) + itemId: createItemId(repository, revision, path) }; } export function fetchSourcesFailure( repository: Repository, + revision: string, + path: string, error: Error ): Action { return { type: FETCH_SOURCES_FAILURE, payload: error, - itemId: createItemId(repository) + itemId: createItemId(repository, revision, path) }; } -function createItemId(repository: Repository) { - return `${repository.namespace}/${repository.name}`; +function createItemId(repository: Repository, revision: string, path: string) { + const revPart = revision ? revision : "_"; + const pathPart = path ? path : ""; + return `${repository.namespace}/${repository.name}/${revPart}/${pathPart}`; } // reducer @@ -83,24 +108,38 @@ export default function reducer( export function getSources( state: any, - repository: Repository + repository: Repository, + revision: string, + path: string ): ?SourcesCollection { if (state.sources) { - return state.sources[createItemId(repository)]; + return state.sources[createItemId(repository, revision, path)]; } return null; } export function isFetchSourcesPending( state: any, - repository: Repository + repository: Repository, + revision: string, + path: string ): boolean { - return isPending(state, FETCH_SOURCES, createItemId(repository)); + return isPending( + state, + FETCH_SOURCES, + createItemId(repository, revision, path) + ); } export function getFetchSourcesFailure( state: any, - repository: Repository + repository: Repository, + revision: string, + path: string ): ?Error { - return getFailure(state, FETCH_SOURCES, createItemId(repository)); + return getFailure( + state, + FETCH_SOURCES, + createItemId(repository, revision, path) + ); } diff --git a/scm-ui/src/repos/sources/modules/sources.test.js b/scm-ui/src/repos/sources/modules/sources.test.js index e047aaeb34..068fa39e8f 100644 --- a/scm-ui/src/repos/sources/modules/sources.test.js +++ b/scm-ui/src/repos/sources/modules/sources.test.js @@ -18,7 +18,7 @@ import { } from "./sources"; const sourcesUrl = - "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources"; + "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/"; const repository: Repository = { name: "core", @@ -91,10 +91,10 @@ describe("sources fetch", () => { fetchMock.getOnce(sourcesUrl, collection); const expectedActions = [ - { type: FETCH_SOURCES_PENDING, itemId: "scm/core" }, + { type: FETCH_SOURCES_PENDING, itemId: "scm/core/_/" }, { type: FETCH_SOURCES_SUCCESS, - itemId: "scm/core", + itemId: "scm/core/_/", payload: collection } ]; @@ -105,6 +105,24 @@ describe("sources fetch", () => { }); }); + it("should fetch the sources of the repository with the given revision and path", () => { + fetchMock.getOnce(sourcesUrl + "abc/src", collection); + + const expectedActions = [ + { type: FETCH_SOURCES_PENDING, itemId: "scm/core/abc/src" }, + { + type: FETCH_SOURCES_SUCCESS, + itemId: "scm/core/abc/src", + payload: collection + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchSources(repository, "abc", "src")).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + it("should dispatch FETCH_SOURCES_FAILURE on server error", () => { fetchMock.getOnce(sourcesUrl, { status: 500 @@ -115,7 +133,7 @@ describe("sources fetch", () => { const actions = store.getActions(); expect(actions[0].type).toBe(FETCH_SOURCES_PENDING); expect(actions[1].type).toBe(FETCH_SOURCES_FAILURE); - expect(actions[1].itemId).toBe("scm/core"); + expect(actions[1].itemId).toBe("scm/core/_/"); expect(actions[1].payload).toBeDefined(); }); }); @@ -127,13 +145,25 @@ describe("reducer tests", () => { expect(reducer(state)).toBe(state); }); - it("should store the collection", () => { + it("should store the collection, without revision and path", () => { const expectedState = { - "scm/core": collection + "scm/core/_/": collection }; - expect(reducer({}, fetchSourcesSuccess(repository, collection))).toEqual( - expectedState - ); + expect( + reducer({}, fetchSourcesSuccess(repository, null, null, collection)) + ).toEqual(expectedState); + }); + + it("should store the collection, with revision and path", () => { + const expectedState = { + "scm/core/abc/src/main": collection + }; + expect( + reducer( + {}, + fetchSourcesSuccess(repository, "abc", "src/main", collection) + ) + ).toEqual(expectedState); }); }); @@ -142,19 +172,28 @@ describe("selector tests", () => { expect(getSources({}, repository)).toBeFalsy(); }); - it("should return the source collection", () => { + it("should return the source collection without revision and path", () => { const state = { sources: { - "scm/core": collection + "scm/core/_/": collection } }; expect(getSources(state, repository)).toBe(collection); }); + it("should return the source collection without revision and path", () => { + const state = { + sources: { + "scm/core/abc/src/main": collection + } + }; + expect(getSources(state, repository, "abc", "src/main")).toBe(collection); + }); + it("should return true, when fetch sources is pending", () => { const state = { pending: { - [FETCH_SOURCES + "/scm/core"]: true + [FETCH_SOURCES + "/scm/core/_/"]: true } }; expect(isFetchSourcesPending(state, repository)).toEqual(true); @@ -169,7 +208,7 @@ describe("selector tests", () => { it("should return error when fetch sources did fail", () => { const state = { failure: { - [FETCH_SOURCES + "/scm/core"]: error + [FETCH_SOURCES + "/scm/core/_/"]: error } }; expect(getFetchSourcesFailure(state, repository)).toEqual(error); From 7b807fa880c705493869479bbc1a69ce649a941d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 28 Sep 2018 12:12:56 +0200 Subject: [PATCH 28/96] keep entered revision in source browser --- .../src/repos/sources/components/FileTree.js | 11 +++++++-- .../repos/sources/components/FileTreeLeaf.js | 21 ++++++++++++---- .../sources/components/FileTreeLeaf.test.js | 24 +++++++++++++++++++ .../src/repos/sources/containers/Sources.js | 11 +++++++-- 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 scm-ui/src/repos/sources/components/FileTreeLeaf.test.js diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 05a775c77a..d606ece0b8 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -13,6 +13,7 @@ const styles = { type Props = { tree: SourcesCollection, + revision: string, path: string, baseUrl: string, @@ -35,8 +36,14 @@ export function findParent(path: string) { class FileTree extends React.Component { render() { - const { tree, path, baseUrl, classes, t } = this.props; - const baseUrlWithRevision = baseUrl + "/" + tree.revision; + const { tree, revision, path, baseUrl, classes, t } = this.props; + + let baseUrlWithRevision = baseUrl; + if (revision) { + baseUrlWithRevision += "/" + revision; + } else { + baseUrlWithRevision += "/" + tree.revision; + } const files = []; if (path) { diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 29d7d35ebe..7751f4dff9 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -21,13 +21,24 @@ type Props = { classes: any }; +export function createLink(base: string, file: File) { + let link = base; + if (file.path) { + let path = file.path; + if (path.startsWith("/")) { + path = path.substring(1); + } + link += "/" + path; + } + if (!link.endsWith("/")) { + link += "/"; + } + return link; +} + class FileTreeLeaf extends React.Component { createLink = (file: File) => { - let link = this.props.baseUrl; - if (file.path) { - link += "/" + file.path + "/"; - } - return link; + return createLink(this.props.baseUrl, file); }; createFileIcon = (file: File) => { diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.test.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.test.js new file mode 100644 index 0000000000..d5004521c8 --- /dev/null +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.test.js @@ -0,0 +1,24 @@ +// @flow + +import { createLink } from "./FileTreeLeaf"; +import type { File } from "@scm-manager/ui-types"; + +describe("create link tests", () => { + function dir(path: string): File { + return { + name: "dir", + path: path, + directory: true + }; + } + + it("should create link", () => { + expect(createLink("src", dir("main"))).toBe("src/main/"); + expect(createLink("src", dir("/main"))).toBe("src/main/"); + expect(createLink("src", dir("/main/"))).toBe("src/main/"); + }); + + it("should return base url if the directory path is empty", () => { + expect(createLink("src", dir(""))).toBe("src/"); + }); +}); diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 12a1a29759..fcfd0c284a 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -37,7 +37,7 @@ class Sources extends React.Component { } render() { - const { sources, path, baseUrl, loading, error } = this.props; + const { sources, revision, path, baseUrl, loading, error } = this.props; if (error) { return ; @@ -47,7 +47,14 @@ class Sources extends React.Component { return ; } - return ; + return ( + + ); } } From f46b0b07603930677777e23e2b6936aa4f3bd590 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 1 Oct 2018 12:57:10 +0200 Subject: [PATCH 29/96] update ui-bundler to v0.0.17 --- scm-plugins/scm-git-plugin/package.json | 2 +- scm-plugins/scm-git-plugin/yarn.lock | 7 +++---- scm-plugins/scm-hg-plugin/package.json | 2 +- scm-plugins/scm-hg-plugin/yarn.lock | 7 +++---- scm-plugins/scm-svn-plugin/package.json | 2 +- scm-plugins/scm-svn-plugin/yarn.lock | 7 +++---- scm-ui-components/packages/ui-components/package.json | 2 +- scm-ui-components/packages/ui-components/yarn.lock | 7 +++---- scm-ui-components/packages/ui-types/package.json | 2 +- scm-ui-components/packages/ui-types/yarn.lock | 7 +++---- scm-ui/package.json | 2 +- scm-ui/yarn.lock | 7 +++---- 12 files changed, 24 insertions(+), 30 deletions(-) diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 93ec098597..8f0ab90d34 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index 702e28711f..c1abfd6640 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -726,7 +726,6 @@ 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" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index c5907d38bc..a2ebd0e6ad 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index 8822bd5e57..c3e8cc476f 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -660,7 +660,6 @@ 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" diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 118108c882..b05332e6ca 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index 8822bd5e57..c3e8cc476f 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -660,7 +660,6 @@ 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" diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index e515002728..708412c874 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -12,7 +12,7 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15", + "@scm-manager/ui-bundler": "^0.0.17", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index bab2b75068..02958734e0 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -728,9 +728,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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -747,7 +747,6 @@ 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" diff --git a/scm-ui-components/packages/ui-types/package.json b/scm-ui-components/packages/ui-types/package.json index 9c06fc7740..0701870562 100644 --- a/scm-ui-components/packages/ui-types/package.json +++ b/scm-ui-components/packages/ui-types/package.json @@ -14,7 +14,7 @@ "check": "flow check" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" }, "browserify": { "transform": [ diff --git a/scm-ui-components/packages/ui-types/yarn.lock b/scm-ui-components/packages/ui-types/yarn.lock index 24213220d9..58b579d6ae 100644 --- a/scm-ui-components/packages/ui-types/yarn.lock +++ b/scm-ui-components/packages/ui-types/yarn.lock @@ -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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -726,7 +726,6 @@ 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" diff --git a/scm-ui/package.json b/scm-ui/package.json index 50ae39cd29..36abcb44a1 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -42,7 +42,7 @@ "pre-commit": "jest && flow && eslint src" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15", + "@scm-manager/ui-bundler": "^0.0.17", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 26bfec8070..5c3f5c3a63 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -732,9 +732,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.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@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" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -751,7 +751,6 @@ 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" From e5a3cbb50ee330b88f91c18858a0716a69204a21 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Oct 2018 14:38:27 +0200 Subject: [PATCH 30/96] change browse api in order to build a tree base file structure --- .../sonia/scm/repository/BrowserResult.java | 246 +++--------------- .../java/sonia/scm/repository/FileObject.java | 37 +++ .../scm/repository/PreProcessorUtil.java | 16 +- .../repository/api/BrowseCommandBuilder.java | 11 - .../sonia/scm/repository/FileObjectTest.java | 32 +++ 5 files changed, 118 insertions(+), 224 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/repository/FileObjectTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java b/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java index 212ce45f81..17c447eb87 100644 --- a/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java @@ -1,19 +1,19 @@ /** * Copyright (c) 2010, Sebastian Sdorra * All rights reserved. - * + *

    * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + *

    * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + *

    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -24,13 +24,11 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

    * http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -40,12 +38,8 @@ import com.google.common.base.Objects; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; -import java.util.Iterator; -import java.util.List; //~--- JDK imports ------------------------------------------------------------ @@ -56,224 +50,56 @@ import java.util.List; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "browser-result") -public class BrowserResult implements Iterable, Serializable -{ +public class BrowserResult implements Serializable { - /** Field description */ - private static final long serialVersionUID = 2818662048045182761L; + private String revision; + private FileObject file; - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ - public BrowserResult() {} - - /** - * Constructs ... - * - * - * @param revision - * @param tag - * @param branch - * @param files - */ - public BrowserResult(String revision, String tag, String branch, - List files) - { - this.revision = revision; - this.tag = tag; - this.branch = branch; - this.files = files; + public BrowserResult() { } - //~--- methods -------------------------------------------------------------- + public BrowserResult(String revision, FileObject file) { + this.revision = revision; + this.file = file; + } + + public String getRevision() { + return revision; + } + + public FileObject getFile() { + return file; + } - /** - * {@inheritDoc} - * - * - * @param obj - * - * @return - */ @Override - public boolean equals(Object obj) - { - if (obj == null) - { + public boolean equals(Object obj) { + if (obj == null) { return false; } - if (getClass() != obj.getClass()) - { + if (getClass() != obj.getClass()) { return false; } final BrowserResult other = (BrowserResult) obj; return Objects.equal(revision, other.revision) - && Objects.equal(tag, other.tag) - && Objects.equal(branch, other.branch) - && Objects.equal(files, other.files); + && Objects.equal(file, other.file); } - /** - * {@inheritDoc} - * - * - * @return - */ @Override - public int hashCode() - { - return Objects.hashCode(revision, tag, branch, files); + public int hashCode() { + return Objects.hashCode(revision, file); } - /** - * Method description - * - * - * @return - */ + @Override - public Iterator iterator() - { - Iterator it = null; - - if (files != null) - { - it = files.iterator(); - } - - return it; - } - - /** - * {@inheritDoc} - * - * - * @return - */ - @Override - public String toString() - { - //J- + public String toString() { return MoreObjects.toStringHelper(this) - .add("revision", revision) - .add("tag", tag) - .add("branch", branch) - .add("files", files) - .toString(); - //J+ + .add("revision", revision) + .add("files", file) + .toString(); } - //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @return - */ - public String getBranch() - { - return branch; - } - - /** - * Method description - * - * - * @return - */ - public List getFiles() - { - return files; - } - - /** - * Method description - * - * - * @return - */ - public String getRevision() - { - return revision; - } - - /** - * Method description - * - * - * @return - */ - public String getTag() - { - return tag; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param branch - */ - public void setBranch(String branch) - { - this.branch = branch; - } - - /** - * Method description - * - * - * @param files - */ - public void setFiles(List files) - { - this.files = files; - } - - /** - * Method description - * - * - * @param revision - */ - public void setRevision(String revision) - { - this.revision = revision; - } - - /** - * Method description - * - * - * @param tag - */ - public void setTag(String tag) - { - this.tag = tag; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String branch; - - /** Field description */ - @XmlElement(name = "file") - @XmlElementWrapper(name = "files") - private List files; - - /** Field description */ - private String revision; - - /** Field description */ - private String tag; } diff --git a/scm-core/src/main/java/sonia/scm/repository/FileObject.java b/scm-core/src/main/java/sonia/scm/repository/FileObject.java index 5279921257..135cefcc7b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/FileObject.java +++ b/scm-core/src/main/java/sonia/scm/repository/FileObject.java @@ -37,6 +37,7 @@ package sonia.scm.repository; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import com.google.common.base.Strings; import sonia.scm.LastModifiedAware; import javax.xml.bind.annotation.XmlAccessType; @@ -44,6 +45,8 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; //~--- JDK imports ------------------------------------------------------------ @@ -181,6 +184,22 @@ public class FileObject implements LastModifiedAware, Serializable return path; } + /** + * Returns the parent path of the file. + * + * @return parent path + */ + public String getParentPath() { + if (Strings.isNullOrEmpty(path)) { + return null; + } + int index = path.lastIndexOf('/'); + if (index > 0) { + return path.substring(0, index); + } + return ""; + } + /** * Return sub repository informations or null if the file is not * sub repository. @@ -284,6 +303,22 @@ public class FileObject implements LastModifiedAware, Serializable this.subRepository = subRepository; } + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(FileObject child) { + this.children.add(child); + } + + public boolean hasChildren() { + return !children.isEmpty(); + } + //~--- fields --------------------------------------------------------------- /** file description */ @@ -307,4 +342,6 @@ public class FileObject implements LastModifiedAware, Serializable /** sub repository informations */ @XmlElement(name = "subrepository") private SubRepository subRepository; + + private List children = new ArrayList<>(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java b/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java index 2a1d9c0340..e64979dde6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java @@ -161,11 +161,21 @@ public class PreProcessorUtil { if (logger.isTraceEnabled()) { - logger.trace("prepare browser result of repository {} for return", - repository.getName()); + logger.trace("prepare browser result of repository {} for return", repository.getName()); } - handlePreProcessForIterable(repository, result,fileObjectPreProcessorFactorySet, fileObjectPreProcessorSet); + PreProcessorHandler handler = new PreProcessorHandler<>(fileObjectPreProcessorFactorySet, fileObjectPreProcessorSet, repository); + handlePreProcessorForFileObject(handler, result.getFile()); + } + + private void handlePreProcessorForFileObject(PreProcessorHandler handler, FileObject fileObject) { + if (fileObject.isDirectory()) { + for (FileObject child : fileObject.getChildren()) { + handlePreProcessorForFileObject(handler, child); + } + } + handler.callPreProcessorFactories(fileObject); + handler.callPreProcessors(fileObject); } /** diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java index fe39aa0a05..2cf7ae5a86 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java @@ -42,7 +42,6 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; -import sonia.scm.repository.FileObjectNameComparator; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryCacheKey; @@ -52,8 +51,6 @@ import sonia.scm.repository.spi.BrowseCommandRequest; import java.io.IOException; import java.io.Serializable; -import java.util.Collections; -import java.util.List; //~--- JDK imports ------------------------------------------------------------ @@ -180,14 +177,6 @@ public final class BrowseCommandBuilder if (!disablePreProcessors && (result != null)) { preProcessorUtil.prepareForReturn(repository, result); - - List fileObjects = result.getFiles(); - - if (fileObjects != null) - { - Collections.sort(fileObjects, FileObjectNameComparator.instance); - result.setFiles(fileObjects); - } } return result; diff --git a/scm-core/src/test/java/sonia/scm/repository/FileObjectTest.java b/scm-core/src/test/java/sonia/scm/repository/FileObjectTest.java new file mode 100644 index 0000000000..bbd9d0d483 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/FileObjectTest.java @@ -0,0 +1,32 @@ +package sonia.scm.repository; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class FileObjectTest { + + @Test + public void getParentPath() { + FileObject file = create("a/b/c"); + assertEquals("a/b", file.getParentPath()); + } + + @Test + public void getParentPathWithoutParent() { + FileObject file = create("a"); + assertEquals("", file.getParentPath()); + } + + @Test + public void getParentPathOfRoot() { + FileObject file = create(""); + assertNull(file.getParentPath()); + } + + private FileObject create(String path) { + FileObject file = new FileObject(); + file.setPath(path); + return file; + } +} From ed9f5fc69e6a049f138f3762d5b223456bbdd7ee Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Oct 2018 14:39:11 +0200 Subject: [PATCH 31/96] implement new browse api for git --- .../scm/repository/spi/GitBrowseCommand.java | 139 +++++++++--------- .../repository/spi/GitBrowseCommandTest.java | 58 +++++--- 2 files changed, 110 insertions(+), 87 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index f194796bdc..b07b7b507d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -35,6 +35,9 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.eclipse.jgit.errors.MissingObjectException; @@ -61,6 +64,7 @@ import sonia.scm.repository.SubRepository; import sonia.scm.util.Util; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -107,6 +111,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.debug("try to create browse result for {}", request); BrowserResult result; + org.eclipse.jgit.lib.Repository repo = open(); ObjectId revId; @@ -121,7 +126,7 @@ public class GitBrowseCommand extends AbstractGitCommand if (revId != null) { - result = getResult(repo, request, revId); + result = new BrowserResult(revId.getName(), getEntry(repo, request, revId)); } else { @@ -134,8 +139,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.warn("coul not find head of repository, empty?"); } - result = new BrowserResult(Constants.HEAD, null, null, - Collections.EMPTY_LIST); + result = new BrowserResult(Constants.HEAD, createEmtpyRoot()); } return result; @@ -143,6 +147,14 @@ public class GitBrowseCommand extends AbstractGitCommand //~--- methods -------------------------------------------------------------- + private FileObject createEmtpyRoot() { + FileObject fileObject = new FileObject(); + fileObject.setName(""); + fileObject.setPath(""); + fileObject.setDirectory(true); + return fileObject; + } + /** * Method description * @@ -193,7 +205,6 @@ public class GitBrowseCommand extends AbstractGitCommand if (!file.isDirectory() &&!request.isDisableLastCommit()) { logger.trace("fetch last commit for {} at {}", path, revId.getName()); - RevCommit commit = getLatestCommit(repo, revId, path); if (commit != null) @@ -265,22 +276,19 @@ public class GitBrowseCommand extends AbstractGitCommand return result; } - private BrowserResult getResult(org.eclipse.jgit.lib.Repository repo, - BrowseCommandRequest request, ObjectId revId) - throws IOException, RevisionNotFoundException { - BrowserResult result = null; + private FileObject getEntry(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId) throws IOException, RevisionNotFoundException { RevWalk revWalk = null; TreeWalk treeWalk = null; - try - { - if (logger.isDebugEnabled()) - { - logger.debug("load repository browser for revision {}", revId.name()); - } + FileObject result; + + try { + logger.debug("load repository browser for revision {}", revId.name()); treeWalk = new TreeWalk(repo); - treeWalk.setRecursive(request.isRecursive()); + if (!Strings.isNullOrEmpty(request.getPath())) { + treeWalk.setFilter(PathFilter.create(request.getPath())); + } revWalk = new RevWalk(repo); RevTree tree = revWalk.parseTree(revId); @@ -291,65 +299,21 @@ public class GitBrowseCommand extends AbstractGitCommand } else { + // TODO throw exception logger.error("could not find tree for {}", revId.name()); } - result = new BrowserResult(); - - List files = Lists.newArrayList(); - - String path = request.getPath(); - - if (Util.isEmpty(path)) - { - while (treeWalk.next()) - { - FileObject fo = createFileObject(repo, request, revId, treeWalk); - - if (fo != null) - { - files.add(fo); - } - } - } - else - { - String[] parts = path.split("/"); - int current = 0; - int limit = parts.length; - - while (treeWalk.next()) - { - String name = treeWalk.getNameString(); - - if (current >= limit) - { - String p = treeWalk.getPathString(); - - if (p.split("/").length > limit) - { - FileObject fo = createFileObject(repo, request, revId, treeWalk); - - if (fo != null) - { - files.add(fo); - } - } - } - else if (name.equalsIgnoreCase(parts[current])) - { - current++; - - if (!request.isRecursive()) - { - treeWalk.enterSubtree(); - } - } + if (Strings.isNullOrEmpty(request.getPath())) { + result = createEmtpyRoot(); + findChildren(result, repo, request, revId, treeWalk); + } else { + result = first(repo, request, revId, treeWalk); + if ( result.isDirectory() ) { + treeWalk.enterSubtree(); + findChildren(result, repo, request, revId, treeWalk); } } - result.setFiles(files); - result.setRevision(revId.getName()); } finally { @@ -360,6 +324,45 @@ public class GitBrowseCommand extends AbstractGitCommand return result; } + private FileObject findChildren(FileObject parent, org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { + List files = Lists.newArrayList(); + while (treeWalk.next()) + { + + FileObject fo = createFileObject(repo, request, revId, treeWalk); + if (!fo.getPath().startsWith(parent.getPath())) { + parent.setChildren(files); + return fo; + } + + + if (fo != null) + { + files.add(fo); + } + + if (request.isRecursive() && fo.isDirectory()) { + treeWalk.enterSubtree(); + FileObject rc = findChildren(fo, repo, request, revId, treeWalk); + if (rc != null) { + files.add(rc); + } + } + } + + parent.setChildren(files); + + return null; + } + + private FileObject first(org.eclipse.jgit.lib.Repository repo, + BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { + if (!treeWalk.next()) { + throw new IOException("tree seams to be empty"); + } + return createFileObject(repo, request, revId, treeWalk); + } + @SuppressWarnings("unchecked") private Map getSubRepositories(org.eclipse.jgit.lib.Repository repo, diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index d71c85a152..9029e732d0 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -58,6 +58,15 @@ import static org.junit.Assert.assertTrue; */ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { + + @Test + public void testGetFile() throws IOException, RevisionNotFoundException { + BrowseCommandRequest request = new BrowseCommandRequest(); + request.setPath("a.txt"); + BrowserResult result = createCommand().getBrowserResult(request); + FileObject fileObject = result.getFile(); + assertEquals("a.txt", fileObject.getName()); + } /** * Test browse command with default branch. @@ -65,12 +74,13 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase @Test public void testDefaultBranch() throws IOException, RevisionNotFoundException { // without default branch, the repository head should be used - BrowserResult result = createCommand().getBrowserResult(new BrowseCommandRequest()); - assertNotNull(result); + FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); + assertNotNull(root); - List foList = result.getFiles(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); + assertEquals(4, foList.size()); assertEquals("a.txt", foList.get(0).getName()); @@ -80,10 +90,11 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase // set default branch and fetch again repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch"); - result = createCommand().getBrowserResult(new BrowseCommandRequest()); - assertNotNull(result); - foList = result.getFiles(); + root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); + assertNotNull(root); + + foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); assertEquals(2, foList.size()); @@ -94,12 +105,10 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase @Test public void testBrowse() throws IOException, RevisionNotFoundException { - BrowserResult result = - createCommand().getBrowserResult(new BrowseCommandRequest()); + FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); + assertNotNull(root); - assertNotNull(result); - - List foList = result.getFiles(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -139,11 +148,11 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase request.setPath("c"); - BrowserResult result = createCommand().getBrowserResult(request); + FileObject root = createCommand().getBrowserResult(request).getFile(); + assertNotNull(root); - assertNotNull(result); - List foList = result.getFiles(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -181,20 +190,31 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testRecusive() throws IOException, RevisionNotFoundException { + public void testRecursive() throws IOException, RevisionNotFoundException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setRecursive(true); - BrowserResult result = createCommand().getBrowserResult(request); + FileObject root = createCommand().getBrowserResult(request).getFile(); + assertNotNull(root); - assertNotNull(result); - List foList = result.getFiles(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); - assertEquals(5, foList.size()); + + assertEquals(4, foList.size()); + + assertEquals("a.txt", foList.get(0).getName()); + assertEquals("b.txt", foList.get(1).getName()); + FileObject c = foList.get(2); + assertEquals("c", c.getName()); + assertEquals("f.txt", foList.get(3).getName()); + + List cChilds = c.getChildren(); + assertEquals("d.txt", cChilds.get(0).getName()); + assertEquals("e.txt", cChilds.get(1).getName()); } /** From 791770f87ef1488db25f89b94de65d0ed5e3fe02 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Oct 2018 14:39:56 +0200 Subject: [PATCH 32/96] implemented new browse api for svn --- .../scm/repository/spi/SvnBrowseCommand.java | 103 ++++-------------- .../repository/spi/SvnBrowseCommandTest.java | 27 +++-- 2 files changed, 39 insertions(+), 91 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java index a75adf6b78..43cc1c3c70 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java @@ -35,6 +35,7 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Strings; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,11 +80,11 @@ public class SvnBrowseCommand extends AbstractSvnCommand @Override @SuppressWarnings("unchecked") public BrowserResult getBrowserResult(BrowseCommandRequest request) throws RevisionNotFoundException { - String path = request.getPath(); + String path = Strings.nullToEmpty(request.getPath()); long revisionNumber = SvnUtil.getRevisionNumber(request.getRevision()); if (logger.isDebugEnabled()) { - logger.debug("browser repository {} in path {} at revision {}", repository.getName(), path, revisionNumber); + logger.debug("browser repository {} in path \"{}\" at revision {}", repository.getName(), path, revisionNumber); } BrowserResult result = null; @@ -91,34 +92,21 @@ public class SvnBrowseCommand extends AbstractSvnCommand try { SVNRepository svnRepository = open(); - Collection entries = - svnRepository.getDir(Util.nonNull(path), revisionNumber, null, - (Collection) null); - List children = Lists.newArrayList(); - String basePath = createBasePath(path); - - if (request.isRecursive()) - { - browseRecursive(svnRepository, revisionNumber, request, children, - entries, basePath); - } - else - { - for (SVNDirEntry entry : entries) - { - children.add(createFileObject(request, svnRepository, revisionNumber, - entry, basePath)); - - } - } if (revisionNumber == -1) { revisionNumber = svnRepository.getLatestRevision(); } - result = new BrowserResult(); - result.setRevision(String.valueOf(revisionNumber)); - result.setFiles(children); + SVNDirEntry rootEntry = svnRepository.info(path, revisionNumber); + FileObject root = createFileObject(request, svnRepository, revisionNumber, rootEntry, path); + root.setPath(path); + + if (root.isDirectory()) { + traverse(svnRepository, revisionNumber, request, root, createBasePath(path)); + } + + + result = new BrowserResult(String.valueOf(revisionNumber), root); } catch (SVNException ex) { @@ -130,52 +118,24 @@ public class SvnBrowseCommand extends AbstractSvnCommand //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param svnRepository - * @param revisionNumber - * @param request - * @param children - * @param entries - * @param basePath - * - * @throws SVNException - */ @SuppressWarnings("unchecked") - private void browseRecursive(SVNRepository svnRepository, - long revisionNumber, BrowseCommandRequest request, - List children, Collection entries, String basePath) + private void traverse(SVNRepository svnRepository, long revisionNumber, BrowseCommandRequest request, + FileObject parent, String basePath) throws SVNException { + Collection entries = svnRepository.getDir(parent.getPath(), revisionNumber, null, (Collection) null); for (SVNDirEntry entry : entries) { - FileObject fo = createFileObject(request, svnRepository, revisionNumber, - entry, basePath); + FileObject child = createFileObject(request, svnRepository, revisionNumber, entry, basePath); - children.add(fo); + parent.addChild(child); - if (fo.isDirectory()) - { - Collection subEntries = - svnRepository.getDir(Util.nonNull(fo.getPath()), revisionNumber, - null, (Collection) null); - - browseRecursive(svnRepository, revisionNumber, request, children, - subEntries, createBasePath(fo.getPath())); + if (child.isDirectory() && request.isRecursive()) { + traverse(svnRepository, revisionNumber, request, child, createBasePath(child.getPath())); } } } - /** - * Method description - * - * - * @param path - * - * @return - */ private String createBasePath(String path) { String basePath = Util.EMPTY_STRING; @@ -193,20 +153,6 @@ public class SvnBrowseCommand extends AbstractSvnCommand return basePath; } - /** - * Method description - * - * - * - * - * @param request - * @param repository - * @param revision - * @param entry - * @param path - * - * @return - */ private FileObject createFileObject(BrowseCommandRequest request, SVNRepository repository, long revision, SVNDirEntry entry, String path) { @@ -237,15 +183,6 @@ public class SvnBrowseCommand extends AbstractSvnCommand return fileObject; } - /** - * Method description - * - * - * @param repository - * @param revision - * @param entry - * @param fileObject - */ private void fetchExternalsProperty(SVNRepository repository, long revision, SVNDirEntry entry, FileObject fileObject) { diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java index c4c658ea7a..014f02bdbe 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java @@ -58,6 +58,16 @@ import static org.junit.Assert.assertTrue; public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase { + @Test + public void testBrowseWithFilePath() throws RevisionNotFoundException { + BrowseCommandRequest request = new BrowseCommandRequest(); + request.setPath("a.txt"); + FileObject file = createCommand().getBrowserResult(request).getFile(); + assertEquals("a.txt", file.getName()); + assertFalse(file.isDirectory()); + assertTrue(file.getChildren().isEmpty()); + } + @Test public void testBrowse() throws RevisionNotFoundException { List foList = getRootFromTip(new BrowseCommandRequest()); @@ -92,7 +102,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + List foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -151,15 +161,16 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + List foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); - assertEquals(4, foList.size()); - - for ( FileObject fo : foList ){ - System.out.println(fo); - } + assertEquals(2, foList.size()); + + FileObject c = getFileObject(foList, "c"); + assertEquals("c", c.getName()); + assertTrue(c.isDirectory()); + assertEquals(2, c.getChildren().size()); } /** @@ -208,7 +219,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + List foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); From bc240577f84574d02abba6a687697241d8c9db6a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Oct 2018 14:40:40 +0200 Subject: [PATCH 33/96] implemented new browse api for mercurial --- .../scm/repository/spi/HgBrowseCommand.java | 16 +- .../spi/javahg/HgFileviewCommand.java | 271 +++++++---------- .../resources/sonia/scm/hg/ext/fileview.py | 286 ++++++++++++------ .../sonia/scm/hg/ext/fileview_test.py | 131 ++++++++ .../repository/spi/HgBrowseCommandTest.java | 29 +- 5 files changed, 464 insertions(+), 269 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index 4e4721ba14..19a8724b69 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -35,8 +35,10 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import sonia.scm.repository.BrowserResult; +import sonia.scm.repository.FileObject; import sonia.scm.repository.Repository; import sonia.scm.repository.spi.javahg.HgFileviewCommand; @@ -45,6 +47,7 @@ import java.io.IOException; //~--- JDK imports ------------------------------------------------------------ /** + * Utilizes the mercurial fileview extension in order to support mercurial repository browsing. * * @author Sebastian Sdorra */ @@ -94,16 +97,7 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand cmd.disableSubRepositoryDetection(); } - BrowserResult result = new BrowserResult(); - - result.setFiles(cmd.execute()); - - if (!Strings.isNullOrEmpty(request.getRevision())) { - result.setRevision(request.getRevision()); - } else { - result.setRevision("tip"); - } - - return result; + FileObject file = cmd.execute(); + return new BrowserResult(MoreObjects.firstNonNull(request.getRevision(), "tip"), file); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java index 74695217d2..f351ffa572 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java @@ -50,35 +50,31 @@ import sonia.scm.repository.SubRepository; import java.io.IOException; +import java.util.Deque; +import java.util.LinkedList; import java.util.List; /** + * Mercurial command to list files of a repository. * * @author Sebastian Sdorra */ public class HgFileviewCommand extends AbstractCommand { - /** - * Constructs ... - * - * - * @param repository - */ - public HgFileviewCommand(Repository repository) + private boolean disableLastCommit = false; + + private HgFileviewCommand(Repository repository) { super(repository); } - //~--- methods -------------------------------------------------------------- - /** - * Method description + * Create command for the given repository. * + * @param repository repository * - * @param repository - * - * @return + * @return fileview command */ public static HgFileviewCommand on(Repository repository) { @@ -86,13 +82,11 @@ public class HgFileviewCommand extends AbstractCommand } /** - * Method description + * Disable last commit fetching for file objects. * - * - * @return + * @return {@code this} */ - public HgFileviewCommand disableLastCommit() - { + public HgFileviewCommand disableLastCommit() { disableLastCommit = true; cmdAppend("-d"); @@ -100,132 +94,128 @@ public class HgFileviewCommand extends AbstractCommand } /** - * Method description + * Disables sub repository detection * - * - * @return + * @return {@code this} */ - public HgFileviewCommand disableSubRepositoryDetection() - { + public HgFileviewCommand disableSubRepositoryDetection() { cmdAppend("-s"); return this; } /** - * Method description + * Start file object fetching at the given path. * * - * @return + * @param path path to start fetching * - * @throws IOException + * @return {@code this} */ - public List execute() throws IOException - { - cmdAppend("-t"); - - List files = Lists.newArrayList(); - - HgInputStream stream = launchStream(); - - while (stream.peek() != -1) - { - FileObject file = null; - char type = (char) stream.read(); - - if (type == 'd') - { - file = readDirectory(stream); - } - else if (type == 'f') - { - file = readFile(stream); - } - else if (type == 's') - { - file = readSubRepository(stream); - } - - if (file != null) - { - files.add(file); - } - } - - return files; - } - - /** - * Method description - * - * - * @param path - * - * @return - */ - public HgFileviewCommand path(String path) - { + public HgFileviewCommand path(String path) { cmdAppend("-p", path); return this; } /** - * Method description + * Fetch file objects recursive. * * - * @return + * @return {@code this} */ - public HgFileviewCommand recursive() - { + public HgFileviewCommand recursive() { cmdAppend("-c"); return this; } /** - * Method description + * Use given revision for file view. * + * @param revision revision id, hash, tag or branch * - * @param revision - * - * @return + * @return {@code this} */ - public HgFileviewCommand rev(String revision) - { + public HgFileviewCommand rev(String revision) { cmdAppend("-r", revision); return this; } - //~--- get methods ---------------------------------------------------------- - /** - * Method description + * Executes the mercurial command and parses the output. * - * - * @return - */ - @Override - public String getCommandName() - { - return HgFileviewExtension.NAME; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param stream - * - * @return + * @return file object * * @throws IOException */ - private FileObject readDirectory(HgInputStream stream) throws IOException + public FileObject execute() throws IOException { + cmdAppend("-t"); + + Deque stack = new LinkedList<>(); + + HgInputStream stream = launchStream(); + + FileObject last = null; + while (stream.peek() != -1) { + FileObject file = read(stream); + + while (!stack.isEmpty()) { + FileObject current = stack.peek(); + if (isParent(current, file)) { + current.addChild(file); + break; + } else { + stack.pop(); + } + } + + if (file.isDirectory()) { + stack.push(file); + } + last = file; + } + + if (stack.isEmpty()) { + // if the stack is empty, the requested path is probably a file + return last; + } else { + // if the stack is not empty, the requested path is a directory + return stack.getLast(); + } + } + + private FileObject read(HgInputStream stream) throws IOException { + char type = (char) stream.read(); + + FileObject file; + switch (type) { + case 'd': + file = readDirectory(stream); + break; + case 'f': + file = readFile(stream); + break; + case 's': + file = readSubRepository(stream); + break; + default: + throw new IOException("unknown file object type: " + type); + } + return file; + } + + private boolean isParent(FileObject parent, FileObject child) { + String parentPath = parent.getPath(); + if (parentPath.equals("")) { + return true; + } + return child.getParentPath().equals(parentPath); + } + + private FileObject readDirectory(HgInputStream stream) throws IOException { FileObject directory = new FileObject(); String path = removeTrailingSlash(stream.textUpTo('\0')); @@ -236,18 +226,7 @@ public class HgFileviewCommand extends AbstractCommand return directory; } - /** - * Method description - * - * - * @param stream - * - * @return - * - * @throws IOException - */ - private FileObject readFile(HgInputStream stream) throws IOException - { + private FileObject readFile(HgInputStream stream) throws IOException { FileObject file = new FileObject(); String path = removeTrailingSlash(stream.textUpTo('\n')); @@ -259,8 +238,7 @@ public class HgFileviewCommand extends AbstractCommand DateTime timestamp = stream.dateTimeUpTo(' '); String description = stream.textUpTo('\0'); - if (!disableLastCommit) - { + if (!disableLastCommit) { file.setLastModified(timestamp.getDate().getTime()); file.setDescription(description); } @@ -268,18 +246,7 @@ public class HgFileviewCommand extends AbstractCommand return file; } - /** - * Method description - * - * - * @param stream - * - * @return - * - * @throws IOException - */ - private FileObject readSubRepository(HgInputStream stream) throws IOException - { + private FileObject readSubRepository(HgInputStream stream) throws IOException { FileObject directory = new FileObject(); String path = removeTrailingSlash(stream.textUpTo('\n')); @@ -292,8 +259,7 @@ public class HgFileviewCommand extends AbstractCommand SubRepository subRepository = new SubRepository(url); - if (!Strings.isNullOrEmpty(revision)) - { + if (!Strings.isNullOrEmpty(revision)) { subRepository.setRevision(revision); } @@ -302,48 +268,33 @@ public class HgFileviewCommand extends AbstractCommand return directory; } - /** - * Method description - * - * - * @param path - * - * @return - */ - private String removeTrailingSlash(String path) - { - if (path.endsWith("/")) - { + private String removeTrailingSlash(String path) { + if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } return path; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param path - * - * @return - */ - private String getNameFromPath(String path) - { + private String getNameFromPath(String path) { int index = path.lastIndexOf('/'); - if (index > 0) - { + if (index > 0) { path = path.substring(index + 1); } return path; } - //~--- fields --------------------------------------------------------------- + /** + * Returns the name of the mercurial command. + * + * @return command name + */ + @Override + public String getCommandName() + { + return HgFileviewExtension.NAME; + } - /** Field description */ - private boolean disableLastCommit = false; } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py index 518f229011..439d5e4aa5 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py @@ -32,61 +32,129 @@ Prints date, size and last message of files. """ + +from collections import defaultdict from mercurial import cmdutil,util cmdtable = {} command = cmdutil.command(cmdtable) +FILE_MARKER = '' + +class File_Collector: + + def __init__(self, recursive = False): + self.recursive = recursive + self.structure = defaultdict(dict, ((FILE_MARKER, []),)) + + def attach(self, branch, trunk, dir_only = False): + parts = branch.split('/', 1) + if len(parts) == 1: # branch is a file + if dir_only: + trunk[parts[0]] = defaultdict(dict, ((FILE_MARKER, []),)) + else: + trunk[FILE_MARKER].append(parts[0]) + else: + node, others = parts + if node not in trunk: + trunk[node] = defaultdict(dict, ((FILE_MARKER, []),)) + if self.recursive: + self.attach(others, trunk[node], dir_only) + + def create_path(self, parent, path): + if len(parent) > 0: + newPath = path[len(parent):] + if newPath.startswith("/"): + newPath = newPath[1:] + return newPath + return path + + def collect(self, paths, path = "", dir_only = False): + for p in paths: + if p.startswith(path): + self.attach(self.create_path(path, p), self.structure, dir_only) + +class File_Object: + def __init__(self, directory, path): + self.directory = directory + self.path = path + self.children = [] + self.sub_repository = None + + def get_name(self): + parts = self.path.split("/") + return parts[len(parts) - 1] + + def get_parent(self): + idx = self.path.rfind("/") + if idx > 0: + return self.path[0:idx] + return "" + + def add_child(self, child): + self.children.append(child) + + def __getitem__(self, key): + return self.children[key] + + def __len__(self): + return len(self.children) + + def __repr__(self): + result = self.path + if self.directory: + result += "/" + return result + +class File_Walker: + + def __init__(self, sub_repositories, visitor): + self.visitor = visitor + self.sub_repositories = sub_repositories + + def create_file(self, path): + return File_Object(False, path) + + def create_directory(self, path): + directory = File_Object(True, path) + if path in self.sub_repositories: + directory.sub_repository = self.sub_repositories[path] + return directory + + def visit_file(self, path): + file = self.create_file(path) + self.visit(file) + + def visit_directory(self, path): + file = self.create_directory(path) + self.visit(file) + + def visit(self, file): + self.visitor.visit(file) + + def create_path(self, parent, path): + if len(parent) > 0: + return parent + "/" + path + return path + + def walk(self, structure, parent = ""): + for key, value in structure.iteritems(): + if key == FILE_MARKER: + if value: + for v in value: + self.visit_file(self.create_path(parent, v)) + else: + self.visit_directory(self.create_path(parent, key)) + if isinstance(value, dict): + self.walk(value, self.create_path(parent, key)) + else: + self.visit_directory(self.create_path(parent, value)) + class SubRepository: url = None revision = None -def removeTrailingSlash(path): - if path.endswith('/'): - path = path[0:-1] - return path - -def appendTrailingSlash(path): - if not path.endswith('/'): - path += '/' - return path - -def collectFiles(revCtx, path, files, directories, recursive): - length = 0 - paths = [] - mf = revCtx.manifest() - if path is "": - length = 1 - for f in mf: - paths.append(f) - else: - length = len(path.split('/')) + 1 - directory = path - if not directory.endswith('/'): - directory += '/' - - for f in mf: - if f.startswith(directory): - paths.append(f) - - if not recursive: - for p in paths: - parts = p.split('/') - depth = len(parts) - if depth is length: - file = revCtx[p] - files.append(file) - elif depth > length: - dirpath = '' - for i in range(0, length): - dirpath += parts[i] + '/' - if not dirpath in directories: - directories.append(dirpath) - else: - for p in paths: - files.append(revCtx[p]) - -def createSubRepositoryMap(revCtx): +def collect_sub_repositories(revCtx): subrepos = {} try: hgsub = revCtx.filectx('.hgsub').data().split('\n') @@ -98,7 +166,7 @@ def createSubRepositoryMap(revCtx): subrepos[parts[0].strip()] = subrepo except Exception: pass - + try: hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n') for line in hgsubstate: @@ -109,32 +177,77 @@ def createSubRepositoryMap(revCtx): subrepo.revision = subrev except Exception: pass - + return subrepos - -def printSubRepository(ui, path, subrepository, transport): - format = '%s %s %s\n' - if transport: - format = 's%s\n%s %s\0' - ui.write( format % (appendTrailingSlash(path), subrepository.revision, subrepository.url)) - -def printDirectory(ui, path, transport): - format = '%s\n' - if transport: - format = 'd%s\0' - ui.write( format % path) - -def printFile(ui, repo, file, disableLastCommit, transport): - date = '0 0' - description = 'n/a' - if not disableLastCommit: - linkrev = repo[file.linkrev()] - date = '%d %d' % util.parsedate(linkrev.date()) - description = linkrev.description() - format = '%s %i %s %s\n' - if transport: - format = 'f%s\n%i %s %s\0' - ui.write( format % (file.path(), file.size(), date, description) ) + +class File_Printer: + + def __init__(self, ui, repo, revCtx, disableLastCommit, transport): + self.ui = ui + self.repo = repo + self.revCtx = revCtx + self.disableLastCommit = disableLastCommit + self.transport = transport + + def print_directory(self, path): + format = '%s/\n' + if self.transport: + format = 'd%s/\0' + self.ui.write( format % path) + + def print_file(self, path): + file = self.revCtx[path] + date = '0 0' + description = 'n/a' + if not self.disableLastCommit: + linkrev = self.repo[file.linkrev()] + date = '%d %d' % util.parsedate(linkrev.date()) + description = linkrev.description() + format = '%s %i %s %s\n' + if self.transport: + format = 'f%s\n%i %s %s\0' + self.ui.write( format % (file.path(), file.size(), date, description) ) + + def print_sub_repository(self, path, subrepo): + format = '%s/ %s %s\n' + if self.transport: + format = 's%s/\n%s %s\0' + self.ui.write( format % (path, subrepo.revision, subrepo.url)) + + def visit(self, file): + if file.sub_repository: + self.print_sub_repository(file.path, file.sub_repository) + elif file.directory: + self.print_directory(file.path) + else: + self.print_file(file.path) + +class File_Viewer: + def __init__(self, revCtx, visitor): + self.revCtx = revCtx + self.visitor = visitor + self.sub_repositories = {} + self.recursive = False + + def remove_ending_slash(self, path): + if path.endswith("/"): + return path[:-1] + return path + + def view(self, path = ""): + manifest = self.revCtx.manifest() + if len(path) > 0 and path in manifest: + self.visitor.visit(File_Object(False, path)) + else: + p = self.remove_ending_slash(path) + + collector = File_Collector(self.recursive) + walker = File_Walker(self.sub_repositories, self.visitor) + + self.visitor.visit(File_Object(True, p)) + collector.collect(manifest, p) + collector.collect(self.sub_repositories.keys(), p, True) + walker.walk(collector.structure, p) @command('fileview', [ ('r', 'revision', 'tip', 'revision to print'), @@ -145,23 +258,12 @@ def printFile(ui, repo, file, disableLastCommit, transport): ('t', 'transport', False, 'format the output for command server'), ]) def fileview(ui, repo, **opts): - files = [] - directories = [] - revision = opts['revision'] - if revision == None: - revision = 'tip' - revCtx = repo[revision] - path = opts['path'] - if path.endswith('/'): - path = path[0:-1] - transport = opts['transport'] - collectFiles(revCtx, path, files, directories, opts['recursive']) - if not opts['disableSubRepositoryDetection']: - subRepositories = createSubRepositoryMap(revCtx) - for k, v in subRepositories.iteritems(): - if k.startswith(path): - printSubRepository(ui, k, v, transport) - for d in directories: - printDirectory(ui, d, transport) - for f in files: - printFile(ui, repo, f, opts['disableLastCommit'], transport) + revCtx = repo[opts["revision"]] + subrepos = {} + if not opts["disableSubRepositoryDetection"]: + subrepos = collect_sub_repositories(revCtx) + printer = File_Printer(ui, repo, revCtx, opts["disableLastCommit"], opts["transport"]) + viewer = File_Viewer(revCtx, printer) + viewer.recursive = opts["recursive"] + viewer.sub_repositories = subrepos + viewer.view(opts["path"]) diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py new file mode 100644 index 0000000000..2ce3989d58 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py @@ -0,0 +1,131 @@ +from fileview import File_Viewer, SubRepository +import unittest + +class DummyRevContext(): + + def __init__(self, mf): + self.mf = mf + + def manifest(self): + return self.mf + +class File_Object_Collector(): + + def __init__(self): + self.stack = [] + + def __getitem__(self, key): + if len(self.stack) == 0 and key == 0: + return self.last + return self.stack[key] + + def visit(self, file): + while len(self.stack) > 0: + current = self.stack[-1] + if file.get_parent() == current.path: + current.add_child(file) + break + else: + self.stack.pop() + if file.directory: + self.stack.append(file) + self.last = file + + +class Test_File_Viewer(unittest.TestCase): + + def test_single_file(self): + root = self.collect(["a.txt", "b.txt"], "a.txt") + self.assertFile(root, "a.txt") + + def test_simple(self): + root = self.collect(["a.txt", "b.txt"]) + self.assertFile(root[0], "a.txt") + self.assertFile(root[1], "b.txt") + + def test_recursive(self): + root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "", True) + self.assertChildren(root, ["a", "b", "f.txt", "c"]) + c = root[3] + self.assertDirectory(c, "c") + self.assertChildren(c, ["c/d.txt", "c/e.txt", "c/g"]) + g = c[2] + self.assertDirectory(g, "c/g") + self.assertChildren(g, ["c/g/h.txt"]) + + def test_recursive_with_path(self): + root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "c", True) + self.assertDirectory(root, "c") + self.assertChildren(root, ["c/d.txt", "c/e.txt", "c/g"]) + g = root[2] + self.assertDirectory(g, "c/g") + self.assertChildren(g, ["c/g/h.txt"]) + + def test_recursive_with_deep_path(self): + root = self.collect(["a", "b", "c/d.txt", "c/e.txt", "f.txt", "c/g/h.txt"], "c/g", True) + self.assertDirectory(root, "c/g") + self.assertChildren(root, ["c/g/h.txt"]) + + def test_non_recursive(self): + root = self.collect(["a.txt", "b.txt", "c/d.txt", "c/e.txt", "c/f/g.txt"]) + self.assertDirectory(root, "") + self.assertChildren(root, ["a.txt", "b.txt", "c"]) + c = root[2] + self.assertEmptyDirectory(c, "c") + + def test_non_recursive_with_path(self): + root = self.collect(["a.txt", "b.txt", "c/d.txt", "c/e.txt", "c/f/g.txt"], "c") + self.assertDirectory(root, "c") + self.assertChildren(root, ["c/d.txt", "c/e.txt", "c/f"]) + f = root[2] + self.assertEmptyDirectory(f, "c/f") + + def test_non_recursive_with_path_with_ending_slash(self): + root = self.collect(["c/d.txt"], "c/") + self.assertDirectory(root, "c") + self.assertFile(root[0], "c/d.txt") + + def test_with_sub_directory(self): + revCtx = DummyRevContext(["a.txt", "b/c.txt"]) + collector = File_Object_Collector() + viewer = File_Viewer(revCtx, collector) + sub_repositories = {} + sub_repositories["d"] = SubRepository() + sub_repositories["d"].url = "d" + sub_repositories["d"].revision = "42" + viewer.sub_repositories = sub_repositories + viewer.view() + + d = collector[0][2] + self.assertDirectory(d, "d") + + + def collect(self, paths, path = "", recursive = False): + revCtx = DummyRevContext(paths) + collector = File_Object_Collector() + + viewer = File_Viewer(revCtx, collector) + viewer.recursive = recursive + viewer.view(path) + + return collector[0] + + def assertChildren(self, parent, expectedPaths): + self.assertEqual(len(parent), len(expectedPaths)) + for idx,item in enumerate(parent.children): + self.assertEqual(item.path, expectedPaths[idx]) + + def assertFile(self, file, expectedPath): + self.assertEquals(file.path, expectedPath) + self.assertFalse(file.directory) + + def assertDirectory(self, file, expectedPath): + self.assertEquals(file.path, expectedPath) + self.assertTrue(file.directory) + + def assertEmptyDirectory(self, file, expectedPath): + self.assertDirectory(file, expectedPath) + self.assertTrue(len(file.children) == 0) + +if __name__ == '__main__': + unittest.main() diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java index c53aa8c607..4b52705613 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java @@ -54,8 +54,17 @@ import static org.junit.Assert.assertTrue; * * @author Sebastian Sdorra */ -public class HgBrowseCommandTest extends AbstractHgCommandTestBase -{ +public class HgBrowseCommandTest extends AbstractHgCommandTestBase { + + @Test + public void testBrowseWithFilePath() throws IOException { + BrowseCommandRequest request = new BrowseCommandRequest(); + request.setPath("a.txt"); + FileObject file = new HgBrowseCommand(cmdContext, repository).getBrowserResult(request).getFile(); + assertEquals("a.txt", file.getName()); + assertFalse(file.isDirectory()); + assertTrue(file.getChildren().isEmpty()); + } @Test public void testBrowse() throws IOException { @@ -85,7 +94,9 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + FileObject c = result.getFile(); + assertEquals("c", c.getName()); + List foList = c.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -147,11 +158,16 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + FileObject root = result.getFile(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); - assertEquals(5, foList.size()); + assertEquals(4, foList.size()); + + FileObject c = getFileObject(foList, "c"); + assertTrue(c.isDirectory()); + assertEquals(2, c.getChildren().size()); } //~--- get methods ---------------------------------------------------------- @@ -190,7 +206,8 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase assertNotNull(result); - List foList = result.getFiles(); + FileObject root = result.getFile(); + List foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); From e7c7cb221e0f014a3f2b15f7c0976f897ed7c463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 11:02:55 +0200 Subject: [PATCH 34/96] correct api for tests --- scm-ui/src/repos/modules/changesets.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 65dd46cdbe..0a319a6c61 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -34,9 +34,9 @@ const changesets = {}; describe("changesets", () => { describe("fetching of changesets", () => { - const DEFAULT_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/changesets"; + const DEFAULT_BRANCH_URL = "/api/v2/repositories/foo/bar/changesets"; const SPECIFIC_BRANCH_URL = - "/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; + "/api/v2/repositories/foo/bar/branches/specific/changesets"; const mockStore = configureMockStore([thunk]); afterEach(() => { From a22bb631b2c30638a64ebdbaf67990ca043abfc5 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 9 Oct 2018 11:09:43 +0200 Subject: [PATCH 35/96] start refactoring of dto mapping of BrowserResult and FileObject --- .../api/v2/resources/BrowserResultDto.java | 26 ---------- ...BrowserResultToBrowserResultDtoMapper.java | 49 ------------------- .../BrowserResultToFileObjectDtoMapper.java | 18 +++++++ .../scm/api/v2/resources/FileObjectDto.java | 8 +++ .../FileObjectToFileObjectDtoMapper.java | 10 ++++ .../api/v2/resources/SourceRootResource.java | 8 +-- ...owserResultToFileObjectDtoMapperTest.java} | 31 ++++++------ .../v2/resources/SourceRootResourceTest.java | 22 +++++---- 8 files changed, 68 insertions(+), 104 deletions(-) delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{BrowserResultToBrowserResultDtoMapperTest.java => BrowserResultToFileObjectDtoMapperTest.java} (81%) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java deleted file mode 100644 index 2b49f18fa1..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Links; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.List; - -@Getter -@Setter -@NoArgsConstructor -public class BrowserResultDto extends HalRepresentation { - private String revision; - - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); - } - - public void setFiles(List files) { - this.withEmbedded("files", files); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java deleted file mode 100644 index c877cb0647..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java +++ /dev/null @@ -1,49 +0,0 @@ -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.Links; -import sonia.scm.repository.BrowserResult; -import sonia.scm.repository.FileObject; -import sonia.scm.repository.NamespaceAndName; - -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.List; - -public class BrowserResultToBrowserResultDtoMapper { - - @Inject - private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; - - @Inject - private ResourceLinks resourceLinks; - - public BrowserResultDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { - BrowserResultDto browserResultDto = new BrowserResultDto(); - - browserResultDto.setRevision(browserResult.getRevision()); - - List fileObjectDtoList = new ArrayList<>(); - for (FileObject fileObject : browserResult.getFiles()) { - fileObjectDtoList.add(mapFileObject(fileObject, namespaceAndName, browserResult.getRevision())); - } - - browserResultDto.setFiles(fileObjectDtoList); - this.addLinks(browserResult, browserResultDto, namespaceAndName, path); - return browserResultDto; - } - - private FileObjectDto mapFileObject(FileObject fileObject, NamespaceAndName namespaceAndName, String revision) { - return fileObjectToFileObjectDtoMapper.map(fileObject, namespaceAndName, revision); - } - - private void addLinks(BrowserResult browserResult, BrowserResultDto dto, NamespaceAndName namespaceAndName, String path) { - if (path.equals("/")) { - path = ""; - } - if (browserResult.getRevision() == null) { - throw new IllegalStateException("missing revision in browser result for repository " + namespaceAndName + " and path " + path); - } else { - dto.add(Links.linkingTo().self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)).build()); - } - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java new file mode 100644 index 0000000000..027f07473a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -0,0 +1,18 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.repository.BrowserResult; +import sonia.scm.repository.NamespaceAndName; + +import javax.inject.Inject; + +public class BrowserResultToFileObjectDtoMapper { + + @Inject + private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; + + public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { + FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, path); + fileObjectDto.setRevision( browserResult.getRevision() ); + return fileObjectDto; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index ab4986554a..470199066d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import com.fasterxml.jackson.annotation.JsonInclude; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -7,6 +8,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import java.time.Instant; +import java.util.List; @Getter @Setter @@ -19,10 +21,16 @@ public class FileObjectDto extends HalRepresentation { private int length; private Instant lastModified; private SubRepositoryDto subRepository; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String revision; @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } + + public void setChildren(List children) { + this.withEmbedded("children", children); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 365c0ad4cb..441e111311 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -12,6 +12,9 @@ import sonia.scm.repository.SubRepository; import javax.inject.Inject; +import java.util.List; +import java.util.stream.Collectors; + import static de.otto.edison.hal.Link.link; @Mapper @@ -37,6 +40,13 @@ public abstract class FileObjectToFileObjectDtoMapper implements InstantAttribut } dto.add(links.build()); + if (fileObject.isDirectory() && fileObject.hasChildren()) { + List children = fileObject.getChildren() + .stream() + .map(fo -> map(fo, namespaceAndName, revision)) + .collect(Collectors.toList()); + dto.setChildren(children); + } } private String removeFirstSlash(String source) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index 874ce4f568..d2b9b382d2 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -21,13 +21,13 @@ import java.io.IOException; public class SourceRootResource { private final RepositoryServiceFactory serviceFactory; - private final BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper; + private final BrowserResultToFileObjectDtoMapper browserResultToFileObjectDtoMapper; @Inject - public SourceRootResource(RepositoryServiceFactory serviceFactory, BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper) { + public SourceRootResource(RepositoryServiceFactory serviceFactory, BrowserResultToFileObjectDtoMapper browserResultToFileObjectDtoMapper) { this.serviceFactory = serviceFactory; - this.browserResultToBrowserResultDtoMapper = browserResultToBrowserResultDtoMapper; + this.browserResultToFileObjectDtoMapper = browserResultToFileObjectDtoMapper; } @GET @@ -62,7 +62,7 @@ public class SourceRootResource { BrowserResult browserResult = browseCommand.getBrowserResult(); if (browserResult != null) { - return Response.ok(browserResultToBrowserResultDtoMapper.map(browserResult, namespaceAndName, path)).build(); + return Response.ok(browserResultToFileObjectDtoMapper.map(browserResult, namespaceAndName, path)).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java similarity index 81% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java index 528418a187..2d19928549 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -public class BrowserResultToBrowserResultDtoMapperTest { +public class BrowserResultToFileObjectDtoMapperTest { private final URI baseUri = URI.create("http://example.com/base/"); @SuppressWarnings("unused") // Is injected @@ -35,7 +35,7 @@ public class BrowserResultToBrowserResultDtoMapperTest { private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; @InjectMocks - private BrowserResultToBrowserResultDtoMapper mapper; + private BrowserResultToFileObjectDtoMapper mapper; private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); @@ -77,7 +77,7 @@ public class BrowserResultToBrowserResultDtoMapperTest { public void shouldMapAttributesCorrectly() { BrowserResult browserResult = createBrowserResult(); - BrowserResultDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"), "path"); + FileObjectDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"), "path"); assertEqualAttributes(browserResult, dto); } @@ -87,7 +87,7 @@ public class BrowserResultToBrowserResultDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - BrowserResultDto dto = mapper.map(browserResult, namespaceAndName, "path"); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName, "path"); verify(fileObjectToFileObjectDtoMapper).map(fileObject1, namespaceAndName, "Revision"); verify(fileObjectToFileObjectDtoMapper).map(fileObject2, namespaceAndName, "Revision"); @@ -98,28 +98,27 @@ public class BrowserResultToBrowserResultDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - BrowserResultDto dto = mapper.map(browserResult, namespaceAndName, "path"); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName, "path"); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).contains("path"); } private BrowserResult createBrowserResult() { - BrowserResult browserResult = new BrowserResult(); - browserResult.setRevision("Revision"); - browserResult.setFiles(createFileObjects()); - - return browserResult; + return new BrowserResult("Revision", createFileObject()); } - private List createFileObjects() { - List fileObjects = new ArrayList<>(); + private FileObject createFileObject() { + FileObject file = new FileObject(); + file.setName(""); + file.setPath(""); + file.setDirectory(true); - fileObjects.add(fileObject1); - fileObjects.add(fileObject2); - return fileObjects; + file.addChild(fileObject1); + file.addChild(fileObject2); + return file; } - private void assertEqualAttributes(BrowserResult browserResult, BrowserResultDto dto) { + private void assertEqualAttributes(BrowserResult browserResult, FileObjectDto dto) { assertThat(dto.getRevision()).isEqualTo(browserResult.getRevision()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index c84a74bc92..66c7c487c1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -50,7 +50,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; @InjectMocks - private BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper; + private BrowserResultToFileObjectDtoMapper browserResultToFileObjectDtoMapper; @Before @@ -63,7 +63,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { dto.setLength(1024); when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto); - SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToBrowserResultDtoMapper); + SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper); super.sourceRootResource = Providers.of(sourceRootResource); dispatcher = createDispatcher(getRepositoryRootResource()); } @@ -93,12 +93,9 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Test public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, RevisionNotFoundException { - BrowserResult browserResult = new BrowserResult(); - browserResult.setRevision("revision"); FileObject fileObject = new FileObject(); fileObject.setName("File Object!"); - - browserResult.setFiles(Arrays.asList(fileObject)); + BrowserResult browserResult = new BrowserResult("revision", fileObject); when(browseCommandBuilder.getBrowserResult()).thenReturn(browserResult); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/revision/fileabc"); @@ -121,10 +118,15 @@ public class SourceRootResourceTest extends RepositoryTestBase { } private BrowserResult createBrowserResult() { - return new BrowserResult("revision", "tag", "branch", createFileObjects()); + return new BrowserResult("revision", createFileObject()); } - private List createFileObjects() { + private FileObject createFileObject() { + FileObject parent = new FileObject(); + parent.setName("bar"); + parent.setPath("/foo/bar"); + parent.setDirectory(true); + FileObject fileObject1 = new FileObject(); fileObject1.setName("FO 1"); fileObject1.setDirectory(false); @@ -132,6 +134,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { fileObject1.setPath("/foo/bar/fo1"); fileObject1.setLength(1024L); fileObject1.setLastModified(0L); + parent.addChild(fileObject1); FileObject fileObject2 = new FileObject(); fileObject2.setName("FO 2"); @@ -140,7 +143,8 @@ public class SourceRootResourceTest extends RepositoryTestBase { fileObject2.setPath("/foo/bar/fo2"); fileObject2.setLength(4096L); fileObject2.setLastModified(1234L); + parent.addChild(fileObject2); - return Arrays.asList(fileObject1, fileObject2); + return parent; } } From 0a46c3b367b769f1834e8b3e7f565fb6cfee9ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 11:11:02 +0200 Subject: [PATCH 36/96] show mail only if set --- scm-ui/src/repos/components/ChangesetDetails.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js index c9bc615866..019762d140 100644 --- a/scm-ui/src/repos/components/ChangesetDetails.js +++ b/scm-ui/src/repos/components/ChangesetDetails.js @@ -23,6 +23,16 @@ type Props = { class ChangesetDetails extends React.Component { render() { const { changeset, repository, t, classes } = this.props; + + const mailadress = changeset.author.mail ? ( + + {t("author.mail")} + + + + + ) : null; + return (

    @@ -35,12 +45,7 @@ class ChangesetDetails extends React.Component { - - - - + {mailadress} From 8c1ec57548269bbb5c5340cbb02af982f6c70547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 11:32:47 +0200 Subject: [PATCH 37/96] correct url --- scm-ui/src/repos/modules/branches.test.js | 67 +++++++++++++---------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index d97d9aa7bf..5af33176ec 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -5,31 +5,29 @@ import { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, - fetchBranchesByNamespaceAndName, getBranchesForNamespaceAndNameFromState, getBranchNames + fetchBranchesByNamespaceAndName, + getBranchesForNamespaceAndNameFromState, + getBranchNames } from "./branches"; import reducer from "./branches"; - const namespace = "foo"; const name = "bar"; const key = namespace + "/" + name; -const branch1 = {name: "branch1", revision: "revision1"}; -const branch2 = {name: "branch2", revision: "revision2"}; -const branch3 = {name: "branch3", revision: "revision3"}; - +const branch1 = { name: "branch1", revision: "revision1" }; +const branch2 = { name: "branch2", revision: "revision2" }; +const branch3 = { name: "branch3", revision: "revision3" }; describe("fetch branches", () => { - const URL = "/api/rest/v2/repositories/foo/bar/branches"; + const URL = "/api/v2/repositories/foo/bar/branches"; const mockStore = configureMockStore([thunk]); - afterEach(() => { fetchMock.reset(); fetchMock.restore(); }); - it("should fetch branches", () => { const collection = {}; @@ -37,20 +35,23 @@ describe("fetch branches", () => { const expectedActions = [ { - type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, + type: FETCH_BRANCHES_PENDING, + payload: { namespace, name }, itemId: key }, { type: FETCH_BRANCHES_SUCCESS, - payload: {data: collection, namespace, name}, + payload: { data: collection, namespace, name }, itemId: key } ]; const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { - expect(store.getActions()).toEqual(expectedActions); - }); + return store + .dispatch(fetchBranchesByNamespaceAndName(namespace, name)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); it("should fail fetching branches on HTTP 500", () => { @@ -60,26 +61,28 @@ describe("fetch branches", () => { const expectedActions = [ { - type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, + type: FETCH_BRANCHES_PENDING, + payload: { namespace, name }, itemId: key }, { type: FETCH_BRANCHES_FAILURE, - payload: {error: collection, namespace, name}, + payload: { error: collection, namespace, name }, itemId: key } ]; const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); - }); - }) + return store + .dispatch(fetchBranchesByNamespaceAndName(namespace, name)) + .then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); + }); + }); }); describe("branches reducer", () => { - const branches = { _embedded: { branches: [branch1, branch2] @@ -104,9 +107,11 @@ describe("branches reducer", () => { it("should not delete existing branches from state", () => { const oldState = { - "foo/bar": { byNames: { - "branch3": branch3 - }} + "foo/bar": { + byNames: { + branch3: branch3 + } + } }; const newState = reducer(oldState, action); @@ -123,12 +128,16 @@ describe("branch selectors", () => { branches: { [key]: { byNames: { - "branch1": branch1 + branch1: branch1 } } } }; - const branches = getBranchesForNamespaceAndNameFromState(namespace, name, state); + const branches = getBranchesForNamespaceAndNameFromState( + namespace, + name, + state + ); expect(branches.length).toEqual(1); expect(branches[0]).toEqual(branch1); }); @@ -138,8 +147,8 @@ describe("branch selectors", () => { branches: { [key]: { byNames: { - "branch1": branch1, - "branch2": branch2 + branch1: branch1, + branch2: branch2 } } } From 5cb5bc9bd7b1730d913c65846f43191708d7af1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 11:32:56 +0200 Subject: [PATCH 38/96] use repository instead of namespace and name --- scm-ui/src/repos/containers/ChangesetView.js | 6 +- scm-ui/src/repos/modules/changesets.js | 67 ++++++++------------ scm-ui/src/repos/modules/changesets.test.js | 64 +++++++++++-------- 3 files changed, 68 insertions(+), 69 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 78cc35a6e7..32b8b2df6c 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -5,7 +5,6 @@ import { withRouter } from "react-router-dom"; import type { Changeset, Repository } from "@scm-manager/ui-types"; import { fetchChangesetIfNeeded, - fetchChangesetReset, getChangeset, getFetchChangesetFailure, isFetchChangesetPending @@ -52,7 +51,7 @@ class ChangesetView extends React.Component { if (!changeset || loading) return ; - return ; + return ; } } @@ -73,9 +72,6 @@ const mapDispatchToProps = dispatch => { id: string ) => { dispatch(fetchChangesetIfNeeded(namespace, repoName, id)); - }, - resetForm: (namespace: string, repoName: string, id: string) => { - dispatch(fetchChangesetReset(namespace, repoName, id)); } }; }; diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index b38185599a..27b18145ce 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -9,7 +9,7 @@ import { apiClient } from "@scm-manager/ui-components"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; -import type { Action, PagedCollection } from "@scm-manager/ui-types"; +import type { Action, PagedCollection, Repository } from "@scm-manager/ui-types"; import * as types from "../../modules/types"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; @@ -33,80 +33,73 @@ const REPO_URL = "repositories"; //********added for detailed view of changesets export function fetchChangesetIfNeeded( - namespace: string, - repoName: string, + repository: Repository, id: string ) { return (dispatch: any, getState: any) => { - if (shouldFetchChangeset(getState(), namespace, repoName, id)) { - return dispatch(fetchChangeset(namespace, repoName, id)); + if (shouldFetchChangeset(getState(), repository, id)) { + return dispatch(fetchChangeset(repository, id)); } }; } export function fetchChangeset( - namespace: string, - repoName: string, + repository: Repository, id: string ) { return function(dispatch: any) { - dispatch(fetchChangesetPending(namespace, repoName, id)); + dispatch(fetchChangesetPending(repository, id)); return apiClient - .get(REPO_URL + `/${namespace}/${repoName}/changesets/${id}`) + .get(REPO_URL + `/${repository.namespace}/${repository.name}/changesets/${id}`) .then(response => response.json()) .then(data => - dispatch(fetchChangesetSuccess(data, namespace, repoName, id)) + dispatch(fetchChangesetSuccess(data, repository, id)) ) .catch(err => { - dispatch(fetchChangesetFailure(namespace, repoName, id, err)); + dispatch(fetchChangesetFailure(repository, id, err)); }); }; } export function fetchChangesetPending( - namespace: string, - repoName: string, + repository: Repository, id: string ): Action { return { type: FETCH_CHANGESET_PENDING, payload: { - namespace, - repoName, + repository, id }, - itemId: createItemId(namespace, repoName, id) + itemId: createItemId(repository.namespace, repository.name, id) }; } export function fetchChangesetSuccess( changeset: any, - namespace: string, - repoName: string, + repository: Repository, id: string ): Action { return { type: FETCH_CHANGESET_SUCCESS, - payload: { changeset, namespace, repoName, id }, - itemId: createItemId(namespace, repoName, id) + payload: { changeset, repository, id }, + itemId: createItemId(repository.namespace, repository.name, id) }; } function fetchChangesetFailure( - namespace: string, - repoName: string, + repository: Repository, id: string, error: Error ): Action { return { type: FETCH_CHANGESET_FAILURE, payload: { - namespace, - repoName, + repository, id, error }, - itemId: createItemId(namespace, repoName, id) + itemId: createItemId(repository.namespace, repository.name, id) }; } @@ -234,8 +227,8 @@ function byKeyReducer( //********added for detailed view of changesets case FETCH_CHANGESET_SUCCESS: const _key = createItemId( - action.payload.namespace, - action.payload.repoName + action.payload.repository.namespace, + action.payload.repository.name ); let _oldChangesets = { [_key]: {} }; if (state[_key] !== undefined) { @@ -353,12 +346,11 @@ export function getChangesets( //********added for detailed view of changesets export function getChangeset( state: Object, - namespace: string, - name: string, + repository: Repository, id: string, branch?: string ) { - const key = createItemId(namespace, name, branch); + const key = createItemId(repository.namespace, repository.name, branch); const changesets = state.changesets && state.changesets.byKey && state.changesets.byKey[key] ? state.changesets.byKey[key].byId @@ -371,11 +363,10 @@ export function getChangeset( export function shouldFetchChangeset( state: Object, - namespace: string, - repoName: string, + repository: Repository, id: string ) { - if (getChangeset(state, namespace, repoName, id)) { + if (getChangeset(state, repository, id)) { return false; } return true; @@ -383,20 +374,18 @@ export function shouldFetchChangeset( export function isFetchChangesetPending( state: Object, - namespace: string, - name: string, + repository: Repository, id: string ) { - return isPending(state, FETCH_CHANGESET, createItemId(namespace, name, id)); + return isPending(state, FETCH_CHANGESET, createItemId(repository.namespace, repository.name, id)); } export function getFetchChangesetFailure( state: Object, - namespace: string, - name: string, + repository: Repository, id: string ) { - return getFailure(state, FETCH_CHANGESET, createItemId(namespace, name, id)); + return getFailure(state, FETCH_CHANGESET, createItemId(repository.namespace, repository.name, id)); } //********end of added for detailed view of changesets diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 0a319a6c61..61a4d37636 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -31,6 +31,12 @@ import { import reducer from "./changesets"; const changesets = {}; +//********added for detailed view of changesets +const repository = { + namespace: "foo", + name: "bar" +}; +//********end of added for detailed view of changesets describe("changesets", () => { describe("fetching of changesets", () => { @@ -55,8 +61,10 @@ describe("changesets", () => { type: FETCH_CHANGESET_PENDING, payload: { id: changesetId, - namespace: "foo", - repoName: "bar" + repository: { + name: "bar", + namespace: "foo" + } }, itemId: "foo/bar/" + changesetId }, @@ -65,8 +73,10 @@ describe("changesets", () => { payload: { changeset: {}, id: changesetId, - namespace: "foo", - repoName: "bar" + repository: { + name: "bar", + namespace: "foo" + } }, itemId: "foo/bar/" + changesetId } @@ -74,7 +84,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangeset("foo", "bar", changesetId)) + .dispatch(fetchChangeset(repository, changesetId)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -88,8 +98,10 @@ describe("changesets", () => { type: FETCH_CHANGESET_PENDING, payload: { id: changesetId, - namespace: "foo", - repoName: "bar" + repository: { + name: "bar", + namespace: "foo" + } }, itemId: "foo/bar/" + changesetId } @@ -97,7 +109,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangeset("foo", "bar", changesetId)) + .dispatch(fetchChangeset(repository, changesetId)) .then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_CHANGESET_FAILURE); @@ -126,8 +138,10 @@ describe("changesets", () => { type: FETCH_CHANGESET_PENDING, payload: { id: "id3", - namespace: "foo", - repoName: "bar" + repository: { + name: "bar", + namespace: "foo" + } }, itemId: "foo/bar/" + "id3" }, @@ -136,8 +150,10 @@ describe("changesets", () => { payload: { changeset: {}, id: "id3", - namespace: "foo", - repoName: "bar" + repository: { + name: "bar", + namespace: "foo" + } }, itemId: "foo/bar/" + "id3" } @@ -145,7 +161,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangesetIfNeeded("foo", "bar", "id3")) + .dispatch(fetchChangesetIfNeeded(repository, "id3")) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -171,7 +187,7 @@ describe("changesets", () => { const store = mockStore(state); return expect( - store.dispatch(fetchChangesetIfNeeded("foo", "bar", "id1")) + store.dispatch(fetchChangesetIfNeeded(repository, "id1")) ).toEqual(undefined); }); @@ -431,7 +447,7 @@ describe("changesets", () => { entries: ["id2"] } }, - fetchChangesetSuccess(responseBodySingleChangeset, "foo", "bar", "id3") + fetchChangesetSuccess(responseBodySingleChangeset, repository, "id3") ); expect(newState).toBeDefined(); expect(newState.byKey["foo/bar"].byId["id3"].description).toEqual( @@ -472,7 +488,7 @@ describe("changesets", () => { } } }; - const result = getChangeset(state, "foo", "bar", "id1"); + const result = getChangeset(state, repository, "id1"); expect(result).toEqual({ id: "id1" }); }); @@ -489,7 +505,7 @@ describe("changesets", () => { } } }; - const result = getChangeset(state, "foo", "bar", "id3"); + const result = getChangeset(state, repository, "id3"); expect(result).toEqual(null); }); @@ -506,7 +522,7 @@ describe("changesets", () => { } } }; - const result = shouldFetchChangeset(state, "foo", "bar", "id3"); + const result = shouldFetchChangeset(state, repository, "id3"); expect(result).toEqual(true); }); @@ -523,7 +539,7 @@ describe("changesets", () => { } } }; - const result = shouldFetchChangeset(state, "foo", "bar", "id2"); + const result = shouldFetchChangeset(state, repository, "id2"); expect(result).toEqual(false); }); @@ -534,11 +550,11 @@ describe("changesets", () => { } }; - expect(isFetchChangesetPending(state, "foo", "bar", "id1")).toBeTruthy(); + expect(isFetchChangesetPending(state, repository, "id1")).toBeTruthy(); }); it("should return false, when fetching changeset is not pending", () => { - expect(isFetchChangesetPending({}, "foo", "bar", "id1")).toEqual(false); + expect(isFetchChangesetPending({}, repository, "id1")).toEqual(false); }); it("should return error if fetching changeset failed", () => { @@ -548,13 +564,11 @@ describe("changesets", () => { } }; - expect(getFetchChangesetFailure(state, "foo", "bar", "id1")).toEqual( - error - ); + expect(getFetchChangesetFailure(state, repository, "id1")).toEqual(error); }); it("should return false if fetching changeset did not fail", () => { - expect(getFetchChangesetFailure({}, "foo", "bar", "id1")).toBeUndefined(); + expect(getFetchChangesetFailure({}, repository, "id1")).toBeUndefined(); }); //********end of added for detailed view of changesets From 41e827186d439e71927c0722e3e59548a9b8d6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 11:35:02 +0200 Subject: [PATCH 39/96] correct usage of changed methods --- scm-ui/src/repos/containers/ChangesetView.js | 25 +++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 32b8b2df6c..62f0fc1eac 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -19,12 +19,7 @@ type Props = { repository: Repository, loading: boolean, error: Error, - fetchChangesetIfNeeded: ( - namespace: string, - repoName: string, - id: string - ) => void, - resetForm: (namespace: string, repoName: string, id: string) => void, + fetchChangesetIfNeeded: (repository: Repository, id: string) => void, match: any, t: string => string }; @@ -33,7 +28,7 @@ class ChangesetView extends React.Component { componentDidMount() { const { fetchChangesetIfNeeded, repository } = this.props; const id = this.props.match.params.id; - fetchChangesetIfNeeded(repository.namespace, repository.name, id); + fetchChangesetIfNeeded(repository, id); } render() { @@ -56,22 +51,18 @@ class ChangesetView extends React.Component { } const mapStateToProps = (state, ownProps: Props) => { - const { namespace, name } = ownProps.repository; + const repository = ownProps.repository; const id = ownProps.match.params.id; - const changeset = getChangeset(state, namespace, name, id); - const loading = isFetchChangesetPending(state, namespace, name, id); - const error = getFetchChangesetFailure(state, namespace, name, id); + const changeset = getChangeset(state, repository, id); + const loading = isFetchChangesetPending(state, repository, id); + const error = getFetchChangesetFailure(state, repository, id); return { changeset, error, loading }; }; const mapDispatchToProps = dispatch => { return { - fetchChangesetIfNeeded: ( - namespace: string, - repoName: string, - id: string - ) => { - dispatch(fetchChangesetIfNeeded(namespace, repoName, id)); + fetchChangesetIfNeeded: (repository: Repository, id: string) => { + dispatch(fetchChangesetIfNeeded(repository, id)); } }; }; From 0bd9ccd3d55d19476fada7277e951d1e08cfa375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 09:30:52 +0200 Subject: [PATCH 40/96] Do not filter for path '/' Otherwise jgit's PathFilter will fail. --- .../main/java/sonia/scm/repository/spi/GitBrowseCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index b07b7b507d..25a4ced3fe 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -286,7 +286,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.debug("load repository browser for revision {}", revId.name()); treeWalk = new TreeWalk(repo); - if (!Strings.isNullOrEmpty(request.getPath())) { + if (!Strings.isNullOrEmpty(request.getPath()) && !"/".equals(request.getPath())) { treeWalk.setFilter(PathFilter.create(request.getPath())); } revWalk = new RevWalk(repo); From 8daf4776413b160dc9ee6300b15b40154e7cab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 10:13:50 +0200 Subject: [PATCH 41/96] Use revision instead of path for revision --- .../api/v2/resources/BrowserResultToFileObjectDtoMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 027f07473a..1119e3929c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -11,7 +11,7 @@ public class BrowserResultToFileObjectDtoMapper { private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { - FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, path); + FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult.getRevision()); fileObjectDto.setRevision( browserResult.getRevision() ); return fileObjectDto; } From 4e051a000d162a20ed4541186eaeab49f503ee01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 10:56:41 +0200 Subject: [PATCH 42/96] Adapt integration test to new HAL link name for children --- .../sonia/scm/it/RepositoryAccessITCase.java | 6 ++--- .../java/sonia/scm/it/utils/ScmRequests.java | 26 +++++++------------ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index 3f8832a3f5..7dd548f452 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -201,7 +201,7 @@ public class RepositoryAccessITCase { .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("_embedded.files.find{it.name=='a.txt'}._links.self.href"); + .path("_embedded.children.find{it.name=='a.txt'}._links.self.href"); given() .when() @@ -216,7 +216,7 @@ public class RepositoryAccessITCase { .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("_embedded.files.find{it.name=='subfolder'}._links.self.href"); + .path("_embedded.children.find{it.name=='subfolder'}._links.self.href"); String selfOfSubfolderUrl = given() .when() .get(subfolderSourceUrl) @@ -231,7 +231,7 @@ public class RepositoryAccessITCase { .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("_embedded.files[0]._links.self.href"); + .path("_embedded.children[0]._links.self.href"); given() .when() .get(subfolderContentUrl) diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 41fd9a1290..c1b6ec85d4 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -2,6 +2,8 @@ package sonia.scm.it.utils; import io.restassured.RestAssured; import io.restassured.response.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -25,6 +27,8 @@ import static sonia.scm.it.utils.TestData.createPasswordChangeJson; */ public class ScmRequests { + private static final Logger LOG = LoggerFactory.getLogger(ScmRequests.class); + private String url; private String username; private String password; @@ -46,10 +50,12 @@ public class ScmRequests { * @return the response of the GET request using the given link */ private Response applyGETRequestFromLink(Response response, String linkPropertyName) { - return applyGETRequest(response + String result = response .then() .extract() - .path(linkPropertyName)); + .path(linkPropertyName); + LOG.info("Extracted result {} from response: {}", linkPropertyName, result); + return applyGETRequest(result); } @@ -267,24 +273,12 @@ public class ScmRequests { this.sourcesResponse = sourcesResponse; } - public SourcesResponse assertRevision(Consumer assertRevision) { - String revision = sourcesResponse.then().extract().path("revision"); - assertRevision.accept(revision); - return this; - } - - public SourcesResponse assertFiles(Consumer assertFiles) { - List files = sourcesResponse.then().extract().path("files"); - assertFiles.accept(files); - return this; - } - public AppliedChangesetsRequest requestFileHistory(String fileName) { - return new AppliedChangesetsRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.history.href")); + return new AppliedChangesetsRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.children.find{it.name=='" + fileName + "'}._links.history.href")); } public AppliedSourcesRequest requestSelf(String fileName) { - return new AppliedSourcesRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.self.href")); + return new AppliedSourcesRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.children.find{it.name=='" + fileName + "'}._links.self.href")); } } From ab11acae2d8c565c4684baddf64533bc3c1ff93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 10:59:50 +0200 Subject: [PATCH 43/96] Show modified date only when present and use long for length --- .../main/java/sonia/scm/api/v2/resources/FileObjectDto.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index 470199066d..e42918e967 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -18,7 +18,8 @@ public class FileObjectDto extends HalRepresentation { private String path; private boolean directory; private String description; - private int length; + private long length; + @JsonInclude(JsonInclude.Include.NON_EMPTY) private Instant lastModified; private SubRepositoryDto subRepository; @JsonInclude(JsonInclude.Include.NON_EMPTY) From 63669b67f0f1402be909d8bee802d8db7e785ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 11:49:49 +0200 Subject: [PATCH 44/96] Detect browse command for root dir correctly in git --- .../java/sonia/scm/repository/spi/GitBrowseCommand.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 25a4ced3fe..e459d79653 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -286,7 +286,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.debug("load repository browser for revision {}", revId.name()); treeWalk = new TreeWalk(repo); - if (!Strings.isNullOrEmpty(request.getPath()) && !"/".equals(request.getPath())) { + if (!isRootRequest(request)) { treeWalk.setFilter(PathFilter.create(request.getPath())); } revWalk = new RevWalk(repo); @@ -303,7 +303,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.error("could not find tree for {}", revId.name()); } - if (Strings.isNullOrEmpty(request.getPath())) { + if (isRootRequest(request)) { result = createEmtpyRoot(); findChildren(result, repo, request, revId, treeWalk); } else { @@ -324,6 +324,10 @@ public class GitBrowseCommand extends AbstractGitCommand return result; } + private boolean isRootRequest(BrowseCommandRequest request) { + return Strings.isNullOrEmpty(request.getPath()) || "/".equals(request.getPath()); + } + private FileObject findChildren(FileObject parent, org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { List files = Lists.newArrayList(); while (treeWalk.next()) From fa0f66caf1b83568d2ba554e87f35b553ac45c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 13:27:57 +0200 Subject: [PATCH 45/96] Iterate into correct subdirectory for git browse command --- .../repository/api/BrowseCommandBuilder.java | 3 +- .../scm/repository/spi/BrowseCommand.java | 4 +-- .../scm/repository/spi/GitBrowseCommand.java | 33 +++++++++++++------ .../repository/spi/GitBrowseCommandTest.java | 12 +++---- .../api/v2/resources/SourceRootResource.java | 9 +++-- .../v2/resources/SourceRootResourceTest.java | 8 ++--- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java index 2cf7ae5a86..d2db6856a7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java @@ -38,6 +38,7 @@ package sonia.scm.repository.api; import com.google.common.base.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.repository.BrowserResult; @@ -135,7 +136,7 @@ public final class BrowseCommandBuilder * * @throws IOException */ - public BrowserResult getBrowserResult() throws IOException, RevisionNotFoundException { + public BrowserResult getBrowserResult() throws IOException, NotFoundException { BrowserResult result = null; if (disableCache) diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java index 2c9fff589c..74cf91997d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java @@ -35,8 +35,8 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -60,4 +60,4 @@ public interface BrowseCommand * * @throws IOException */ - BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, RevisionNotFoundException;} + BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, NotFoundException;} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index e459d79653..7eb45c8719 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -35,9 +35,7 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.eclipse.jgit.errors.MissingObjectException; @@ -53,6 +51,7 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitSubModuleParser; @@ -64,7 +63,6 @@ import sonia.scm.repository.SubRepository; import sonia.scm.util.Util; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -107,7 +105,7 @@ public class GitBrowseCommand extends AbstractGitCommand @Override @SuppressWarnings("unchecked") public BrowserResult getBrowserResult(BrowseCommandRequest request) - throws IOException, RevisionNotFoundException { + throws IOException, NotFoundException { logger.debug("try to create browse result for {}", request); BrowserResult result; @@ -276,7 +274,7 @@ public class GitBrowseCommand extends AbstractGitCommand return result; } - private FileObject getEntry(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId) throws IOException, RevisionNotFoundException { + private FileObject getEntry(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId) throws IOException, NotFoundException { RevWalk revWalk = null; TreeWalk treeWalk = null; @@ -328,7 +326,7 @@ public class GitBrowseCommand extends AbstractGitCommand return Strings.isNullOrEmpty(request.getPath()) || "/".equals(request.getPath()); } - private FileObject findChildren(FileObject parent, org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { + private FileObject findChildren(FileObject parent, org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, NotFoundException { List files = Lists.newArrayList(); while (treeWalk.next()) { @@ -360,11 +358,26 @@ public class GitBrowseCommand extends AbstractGitCommand } private FileObject first(org.eclipse.jgit.lib.Repository repo, - BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { - if (!treeWalk.next()) { - throw new IOException("tree seams to be empty"); + BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, NotFoundException { + String[] parts = request.getPath().split("/"); + int current = 0; + int limit = parts.length; + + while (treeWalk.next()) { + String name = treeWalk.getNameString(); + + if (name.equalsIgnoreCase(parts[current])) { + current++; + + if (current >= limit) { + return createFileObject(repo, request, revId, treeWalk); + } else { + treeWalk.enterSubtree(); + } + } } - return createFileObject(repo, request, revId, treeWalk); + + throw new NotFoundException("file", request.getPath()); } @SuppressWarnings("unchecked") diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 9029e732d0..7b00419a43 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -36,10 +36,10 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import org.junit.Test; +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitConstants; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; import java.util.List; @@ -60,7 +60,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { @Test - public void testGetFile() throws IOException, RevisionNotFoundException { + public void testGetFile() throws IOException, NotFoundException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setPath("a.txt"); BrowserResult result = createCommand().getBrowserResult(request); @@ -72,7 +72,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase * Test browse command with default branch. */ @Test - public void testDefaultBranch() throws IOException, RevisionNotFoundException { + public void testDefaultBranch() throws IOException, NotFoundException { // without default branch, the repository head should be used FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); @@ -104,7 +104,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testBrowse() throws IOException, RevisionNotFoundException { + public void testBrowse() throws IOException, NotFoundException { FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); @@ -143,7 +143,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testBrowseSubDirectory() throws IOException, RevisionNotFoundException { + public void testBrowseSubDirectory() throws IOException, NotFoundException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setPath("c"); @@ -190,7 +190,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testRecursive() throws IOException, RevisionNotFoundException { + public void testRecursive() throws IOException, NotFoundException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setRecursive(true); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index d2b9b382d2..ce15a1a76b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -33,14 +31,14 @@ public class SourceRootResource { @GET @Produces(VndMediaType.SOURCE) @Path("") - public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RevisionNotFoundException, RepositoryNotFoundException, IOException { + public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws NotFoundException, IOException { return getSource(namespace, name, "/", null); } @GET @Produces(VndMediaType.SOURCE) @Path("{revision}") - public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws RevisionNotFoundException, RepositoryNotFoundException, IOException { + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws NotFoundException, IOException { return getSource(namespace, name, "/", revision); } @@ -51,7 +49,7 @@ public class SourceRootResource { return getSource(namespace, name, path, revision); } - private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, NotFoundException { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName); try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand(); @@ -59,6 +57,7 @@ public class SourceRootResource { if (revision != null && !revision.isEmpty()) { browseCommand.setRevision(revision); } + browseCommand.setDisableCache(true); BrowserResult browserResult = browseCommand.getBrowserResult(); if (browserResult != null) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 66c7c487c1..c3059355cd 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -10,11 +10,11 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -22,8 +22,6 @@ import sonia.scm.repository.api.RepositoryServiceFactory; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -69,7 +67,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldReturnSources() throws URISyntaxException, IOException, RevisionNotFoundException { + public void shouldReturnSources() throws URISyntaxException, IOException, NotFoundException { BrowserResult result = createBrowserResult(); when(browseCommandBuilder.getBrowserResult()).thenReturn(result); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources"); @@ -92,7 +90,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, RevisionNotFoundException { + public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, NotFoundException { FileObject fileObject = new FileObject(); fileObject.setName("File Object!"); BrowserResult browserResult = new BrowserResult("revision", fileObject); From aa1879f0901ff16c5c0863ea017c83adbd9fae91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 13:52:58 +0200 Subject: [PATCH 46/96] Represent recursive sources structure --- scm-ui-components/packages/ui-types/src/Sources.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-types/src/Sources.js b/scm-ui-components/packages/ui-types/src/Sources.js index 0267212987..e7ea7dafa0 100644 --- a/scm-ui-components/packages/ui-types/src/Sources.js +++ b/scm-ui-components/packages/ui-types/src/Sources.js @@ -17,7 +17,10 @@ export type File = { length: number, lastModified?: string, subRepository?: SubRepository, // TODO - _links: Links + _links: Links, + _embedded: { + children: File[] + } }; export type SourcesCollection = Collection & { From 949252918856f5d40dfd44dcdcc282ea312dcdc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 14:17:38 +0200 Subject: [PATCH 47/96] Replace sources collection with file in frontend --- scm-ui-components/packages/ui-types/src/Sources.js | 7 ------- scm-ui-components/packages/ui-types/src/index.js | 2 +- scm-ui/src/repos/sources/components/FileTree.js | 6 +++--- scm-ui/src/repos/sources/containers/Sources.js | 4 ++-- scm-ui/src/repos/sources/modules/sources.js | 10 +++------- 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/scm-ui-components/packages/ui-types/src/Sources.js b/scm-ui-components/packages/ui-types/src/Sources.js index e7ea7dafa0..3852466635 100644 --- a/scm-ui-components/packages/ui-types/src/Sources.js +++ b/scm-ui-components/packages/ui-types/src/Sources.js @@ -22,10 +22,3 @@ export type File = { children: File[] } }; - -export type SourcesCollection = Collection & { - revision: string, - _embedded: { - files: File[] - } -}; diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index c72b958cab..90500ff2e7 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -13,4 +13,4 @@ export type { Config } from "./Config"; export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions"; -export type { SubRepository, File, SourcesCollection } from "./Sources"; +export type { SubRepository, File } from "./Sources"; diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index d606ece0b8..a2c0445ba8 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -3,7 +3,7 @@ import React from "react"; import { translate } from "react-i18next"; import injectSheet from "react-jss"; import FileTreeLeaf from "./FileTreeLeaf"; -import type { SourcesCollection } from "@scm-manager/ui-types"; +import type { File } from "@scm-manager/ui-types"; const styles = { iconColumn: { @@ -12,7 +12,7 @@ const styles = { }; type Props = { - tree: SourcesCollection, + tree: File, revision: string, path: string, baseUrl: string, @@ -53,7 +53,7 @@ class FileTree extends React.Component { directory: true }); } - files.push(...tree._embedded.files); + files.push(...tree._embedded.children); return (
    {t("author.name")} {changeset.author.name}
    {t("author.mail")} - -
    {t("changeset.description")} {changeset.description}
    diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index fcfd0c284a..c0cb5dde54 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,7 +1,7 @@ // @flow import React from "react"; import { connect } from "react-redux"; -import type { Repository, SourcesCollection } from "@scm-manager/ui-types"; +import type { Repository, File } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import { @@ -13,7 +13,7 @@ import { type Props = { repository: Repository, - sources: SourcesCollection, + sources: File, loading: boolean, error: Error, revision: string, diff --git a/scm-ui/src/repos/sources/modules/sources.js b/scm-ui/src/repos/sources/modules/sources.js index 7e4d21722e..fb606228a5 100644 --- a/scm-ui/src/repos/sources/modules/sources.js +++ b/scm-ui/src/repos/sources/modules/sources.js @@ -1,11 +1,7 @@ // @flow import * as types from "../../../modules/types"; -import type { - Repository, - SourcesCollection, - Action -} from "@scm-manager/ui-types"; +import type { Repository, File, Action } from "@scm-manager/ui-types"; import { apiClient } from "@scm-manager/ui-components"; import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; @@ -61,7 +57,7 @@ export function fetchSourcesSuccess( repository: Repository, revision: string, path: string, - sources: SourcesCollection + sources: File ) { return { type: FETCH_SOURCES_SUCCESS, @@ -111,7 +107,7 @@ export function getSources( repository: Repository, revision: string, path: string -): ?SourcesCollection { +): ?File { if (state.sources) { return state.sources[createItemId(repository, revision, path)]; } From ce3f2e76e8051fa3c829108b1ba30e8a9e570d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 18:17:08 +0200 Subject: [PATCH 48/96] Fix unit test --- .../BrowserResultToFileObjectDtoMapper.java | 6 +++++- .../v2/resources/SourceRootResourceTest.java | 17 ++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 1119e3929c..4dec269c57 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -7,8 +7,12 @@ import javax.inject.Inject; public class BrowserResultToFileObjectDtoMapper { + private final FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; + @Inject - private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; + public BrowserResultToFileObjectDtoMapper(FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper) { + this.fileObjectToFileObjectDtoMapper = fileObjectToFileObjectDtoMapper; + } public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult.getRevision()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index c3059355cd..96b8ac45f7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -24,8 +24,6 @@ import java.net.URI; import java.net.URISyntaxException; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @@ -44,23 +42,18 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Mock private BrowseCommandBuilder browseCommandBuilder; - @Mock - private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; - @InjectMocks + private FileObjectToFileObjectDtoMapperImpl fileObjectToFileObjectDtoMapper; + private BrowserResultToFileObjectDtoMapper browserResultToFileObjectDtoMapper; @Before public void prepareEnvironment() throws Exception { + browserResultToFileObjectDtoMapper = new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(service.getBrowseCommand()).thenReturn(browseCommandBuilder); - FileObjectDto dto = new FileObjectDto(); - dto.setName("name"); - dto.setLength(1024); - - when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto); SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper); super.sourceRootResource = Providers.of(sourceRootResource); dispatcher = createDispatcher(getRepositoryRootResource()); @@ -75,8 +68,9 @@ public class SourceRootResourceTest extends RepositoryTestBase { dispatcher.invoke(request, response); assertThat(response.getStatus()).isEqualTo(200); + System.out.println(response.getContentAsString()); assertThat(response.getContentAsString()).contains("\"revision\":\"revision\""); - assertThat(response.getContentAsString()).contains("\"files\":"); + assertThat(response.getContentAsString()).contains("\"children\":"); } @Test @@ -93,6 +87,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, NotFoundException { FileObject fileObject = new FileObject(); fileObject.setName("File Object!"); + fileObject.setPath("/"); BrowserResult browserResult = new BrowserResult("revision", fileObject); when(browseCommandBuilder.getBrowserResult()).thenReturn(browserResult); From 8c0f8dd8ddbf6bededb24ddc2d8b9c195ce5381c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 18:38:06 +0200 Subject: [PATCH 49/96] Fix unit test --- .../BrowserResultToFileObjectDtoMapper.java | 2 +- .../api/v2/resources/SourceRootResource.java | 2 +- ...rowserResultToFileObjectDtoMapperTest.java | 27 ++++++------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 4dec269c57..9720fb5b1a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -14,7 +14,7 @@ public class BrowserResultToFileObjectDtoMapper { this.fileObjectToFileObjectDtoMapper = fileObjectToFileObjectDtoMapper; } - public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { + public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) { FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult.getRevision()); fileObjectDto.setRevision( browserResult.getRevision() ); return fileObjectDto; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index ce15a1a76b..327283e89e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -61,7 +61,7 @@ public class SourceRootResource { BrowserResult browserResult = browseCommand.getBrowserResult(); if (browserResult != null) { - return Response.ok(browserResultToFileObjectDtoMapper.map(browserResult, namespaceAndName, path)).build(); + return Response.ok(browserResultToFileObjectDtoMapper.map(browserResult, namespaceAndName)).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java index 2d19928549..b0f0d00708 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java @@ -8,21 +8,14 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; -import org.mockito.Mock; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; import java.net.URI; -import java.util.ArrayList; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; public class BrowserResultToFileObjectDtoMapperTest { @@ -31,10 +24,9 @@ public class BrowserResultToFileObjectDtoMapperTest { @SuppressWarnings("unused") // Is injected private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); - @Mock - private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; - @InjectMocks + private FileObjectToFileObjectDtoMapperImpl fileObjectToFileObjectDtoMapper; + private BrowserResultToFileObjectDtoMapper mapper; private final Subject subject = mock(Subject.class); @@ -47,6 +39,7 @@ public class BrowserResultToFileObjectDtoMapperTest { @Before public void init() { initMocks(this); + mapper = new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); subjectThreadState.bind(); ThreadContext.bind(subject); @@ -63,9 +56,6 @@ public class BrowserResultToFileObjectDtoMapperTest { fileObject2.setPath("/path/object/2"); fileObject2.setDescription("description of file object 2"); fileObject2.setDirectory(true); - - when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())) - .thenReturn(new FileObjectDto()); } @After @@ -77,7 +67,7 @@ public class BrowserResultToFileObjectDtoMapperTest { public void shouldMapAttributesCorrectly() { BrowserResult browserResult = createBrowserResult(); - FileObjectDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"), "path"); + FileObjectDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar")); assertEqualAttributes(browserResult, dto); } @@ -87,10 +77,9 @@ public class BrowserResultToFileObjectDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - FileObjectDto dto = mapper.map(browserResult, namespaceAndName, "path"); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName); - verify(fileObjectToFileObjectDtoMapper).map(fileObject1, namespaceAndName, "Revision"); - verify(fileObjectToFileObjectDtoMapper).map(fileObject2, namespaceAndName, "Revision"); + assertThat(dto.getEmbedded().getItemsBy("children")).hasSize(2); } @Test @@ -98,7 +87,7 @@ public class BrowserResultToFileObjectDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - FileObjectDto dto = mapper.map(browserResult, namespaceAndName, "path"); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).contains("path"); } @@ -110,7 +99,7 @@ public class BrowserResultToFileObjectDtoMapperTest { private FileObject createFileObject() { FileObject file = new FileObject(); file.setName(""); - file.setPath(""); + file.setPath("/path"); file.setDirectory(true); file.addChild(fileObject1); From e29681245720ab588d2a86b6a8d52ab876eb7d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Oct 2018 18:54:15 +0200 Subject: [PATCH 50/96] Remove pseudo error handling --- .../scm/repository/spi/GitBrowseCommand.java | 101 +++++++----------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 7eb45c8719..df40efaea4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -168,67 +168,52 @@ public class GitBrowseCommand extends AbstractGitCommand private FileObject createFileObject(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, RevisionNotFoundException { - FileObject file; - try + FileObject file = new FileObject(); + + String path = treeWalk.getPathString(); + + file.setName(treeWalk.getNameString()); + file.setPath(path); + + SubRepository sub = null; + + if (!request.isDisableSubRepositoryDetection()) { - file = new FileObject(); + sub = getSubRepository(repo, revId, path); + } - String path = treeWalk.getPathString(); + if (sub != null) + { + logger.trace("{} seems to be a sub repository", path); + file.setDirectory(true); + file.setSubRepository(sub); + } + else + { + ObjectLoader loader = repo.open(treeWalk.getObjectId(0)); - file.setName(treeWalk.getNameString()); - file.setPath(path); + file.setDirectory(loader.getType() == Constants.OBJ_TREE); + file.setLength(loader.getSize()); - SubRepository sub = null; - - if (!request.isDisableSubRepositoryDetection()) + // don't show message and date for directories to improve performance + if (!file.isDirectory() &&!request.isDisableLastCommit()) { - sub = getSubRepository(repo, revId, path); - } + logger.trace("fetch last commit for {} at {}", path, revId.getName()); + RevCommit commit = getLatestCommit(repo, revId, path); - if (sub != null) - { - logger.trace("{} seems to be a sub repository", path); - file.setDirectory(true); - file.setSubRepository(sub); - } - else - { - ObjectLoader loader = repo.open(treeWalk.getObjectId(0)); - - file.setDirectory(loader.getType() == Constants.OBJ_TREE); - file.setLength(loader.getSize()); - - // don't show message and date for directories to improve performance - if (!file.isDirectory() &&!request.isDisableLastCommit()) + if (commit != null) { - logger.trace("fetch last commit for {} at {}", path, revId.getName()); - RevCommit commit = getLatestCommit(repo, revId, path); - - if (commit != null) - { - file.setLastModified(GitUtil.getCommitTime(commit)); - file.setDescription(commit.getShortMessage()); - } - else if (logger.isWarnEnabled()) - { - logger.warn("could not find latest commit for {} on {}", path, - revId); - } + file.setLastModified(GitUtil.getCommitTime(commit)); + file.setDescription(commit.getShortMessage()); + } + else if (logger.isWarnEnabled()) + { + logger.warn("could not find latest commit for {} on {}", path, + revId); } } } - catch (MissingObjectException ex) - { - file = null; - logger.error("could not fetch object for id {}", revId); - - if (logger.isTraceEnabled()) - { - logger.trace("could not fetch object", ex); - } - } - return file; } @@ -331,21 +316,17 @@ public class GitBrowseCommand extends AbstractGitCommand while (treeWalk.next()) { - FileObject fo = createFileObject(repo, request, revId, treeWalk); - if (!fo.getPath().startsWith(parent.getPath())) { + FileObject fileObject = createFileObject(repo, request, revId, treeWalk); + if (!fileObject.getPath().startsWith(parent.getPath())) { parent.setChildren(files); - return fo; + return fileObject; } + files.add(fileObject); - if (fo != null) - { - files.add(fo); - } - - if (request.isRecursive() && fo.isDirectory()) { + if (request.isRecursive() && fileObject.isDirectory()) { treeWalk.enterSubtree(); - FileObject rc = findChildren(fo, repo, request, revId, treeWalk); + FileObject rc = findChildren(fileObject, repo, request, revId, treeWalk); if (rc != null) { files.add(rc); } From b4c854ee9994d923e3396325e6887fcbcda78314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 11 Oct 2018 09:55:47 +0200 Subject: [PATCH 51/96] Use more generic collection instead of list --- .../java/sonia/scm/repository/FileObject.java | 13 +- .../repository/spi/GitBrowseCommandTest.java | 153 ++++++------------ .../repository/spi/HgBrowseCommandTest.java | 39 ++--- .../repository/spi/SvnBrowseCommandTest.java | 39 ++--- 4 files changed, 80 insertions(+), 164 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/FileObject.java b/scm-core/src/main/java/sonia/scm/repository/FileObject.java index 135cefcc7b..7dedebb13a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/FileObject.java +++ b/scm-core/src/main/java/sonia/scm/repository/FileObject.java @@ -33,8 +33,6 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Strings; @@ -46,9 +44,10 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -//~--- JDK imports ------------------------------------------------------------ +import static java.util.Collections.unmodifiableCollection; /** * The FileObject represents a file or a directory in a repository. @@ -303,12 +302,12 @@ public class FileObject implements LastModifiedAware, Serializable this.subRepository = subRepository; } - public List getChildren() { - return children; + public Collection getChildren() { + return unmodifiableCollection(children); } public void setChildren(List children) { - this.children = children; + this.children = new ArrayList<>(children); } public void addChild(FileObject child) { @@ -343,5 +342,5 @@ public class FileObject implements LastModifiedAware, Serializable @XmlElement(name = "subrepository") private SubRepository subRepository; - private List children = new ArrayList<>(); + private Collection children = new ArrayList<>(); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 7b00419a43..92b7ff69a9 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -6,13 +6,13 @@ * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -26,15 +26,11 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- - import org.junit.Test; import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; @@ -42,22 +38,20 @@ import sonia.scm.repository.FileObject; import sonia.scm.repository.GitConstants; import java.io.IOException; -import java.util.List; +import java.util.Collection; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -//~--- JDK imports ------------------------------------------------------------ - /** * Unit tests for {@link GitBrowseCommand}. - * + * * @author Sebastian Sdorra */ -public class GitBrowseCommandTest extends AbstractGitCommandTestBase -{ +public class GitBrowseCommandTest extends AbstractGitCommandTestBase { @Test public void testGetFile() throws IOException, NotFoundException { @@ -67,40 +61,33 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase FileObject fileObject = result.getFile(); assertEquals("a.txt", fileObject.getName()); } - - /** - * Test browse command with default branch. - */ + @Test - public void testDefaultBranch() throws IOException, NotFoundException { + public void testDefaultDefaultBranch() throws IOException, NotFoundException { // without default branch, the repository head should be used FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); - List foList = root.getChildren(); + Collection foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); - assertEquals(4, foList.size()); - - assertEquals("a.txt", foList.get(0).getName()); - assertEquals("b.txt", foList.get(1).getName()); - assertEquals("c", foList.get(2).getName()); - assertEquals("f.txt", foList.get(3).getName()); - - // set default branch and fetch again + assertThat(foList) + .extracting("name") + .containsExactly("a.txt", "b.txt", "c", "f.txt"); + } + + @Test + public void testExplicitDefaultBranch() throws IOException, NotFoundException { repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch"); - root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); + FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); - foList = root.getChildren(); - assertNotNull(foList); - assertFalse(foList.isEmpty()); - assertEquals(2, foList.size()); - - assertEquals("a.txt", foList.get(0).getName()); - assertEquals("c", foList.get(1).getName()); + Collection foList = root.getChildren(); + assertThat(foList) + .extracting("name") + .containsExactly("a.txt", "c"); } @Test @@ -108,35 +95,18 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); - List foList = root.getChildren(); + Collection foList = root.getChildren(); - assertNotNull(foList); - assertFalse(foList.isEmpty()); - assertEquals(4, foList.size()); + FileObject a = findFile(foList, "a.txt"); + FileObject c = findFile(foList, "c"); - FileObject a = null; - FileObject c = null; - - for (FileObject f : foList) - { - if ("a.txt".equals(f.getName())) - { - a = f; - } - else if ("c".equals(f.getName())) - { - c = f; - } - } - - assertNotNull(a); assertFalse(a.isDirectory()); assertEquals("a.txt", a.getName()); assertEquals("a.txt", a.getPath()); assertEquals("added new line for blame", a.getDescription()); assertTrue(a.getLength() > 0); checkDate(a.getLastModified()); - assertNotNull(c); + assertTrue(c.isDirectory()); assertEquals("c", c.getName()); assertEquals("c", c.getPath()); @@ -149,38 +119,21 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase request.setPath("c"); FileObject root = createCommand().getBrowserResult(request).getFile(); - assertNotNull(root); + Collection foList = root.getChildren(); - List foList = root.getChildren(); + assertThat(foList).hasSize(2); - assertNotNull(foList); - assertFalse(foList.isEmpty()); - assertEquals(2, foList.size()); + FileObject d = findFile(foList, "d.txt"); + FileObject e = findFile(foList, "e.txt"); - FileObject d = null; - FileObject e = null; - - for (FileObject f : foList) - { - if ("d.txt".equals(f.getName())) - { - d = f; - } - else if ("e.txt".equals(f.getName())) - { - e = f; - } - } - - assertNotNull(d); assertFalse(d.isDirectory()); assertEquals("d.txt", d.getName()); assertEquals("c/d.txt", d.getPath()); assertEquals("added file d and e in folder c", d.getDescription()); assertTrue(d.getLength() > 0); checkDate(d.getLastModified()); - assertNotNull(e); + assertFalse(e.isDirectory()); assertEquals("e.txt", e.getName()); assertEquals("c/e.txt", e.getPath()); @@ -196,35 +149,29 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase request.setRecursive(true); FileObject root = createCommand().getBrowserResult(request).getFile(); - assertNotNull(root); + Collection foList = root.getChildren(); - List foList = root.getChildren(); + assertThat(foList) + .extracting("name") + .containsExactly("a.txt", "b.txt", "c", "f.txt"); - assertNotNull(foList); - assertFalse(foList.isEmpty()); + FileObject c = findFile(foList, "c"); - assertEquals(4, foList.size()); - - assertEquals("a.txt", foList.get(0).getName()); - assertEquals("b.txt", foList.get(1).getName()); - FileObject c = foList.get(2); - assertEquals("c", c.getName()); - assertEquals("f.txt", foList.get(3).getName()); - - List cChilds = c.getChildren(); - assertEquals("d.txt", cChilds.get(0).getName()); - assertEquals("e.txt", cChilds.get(1).getName()); + Collection cChildren = c.getChildren(); + assertThat(cChildren) + .extracting("name") + .containsExactly("d.txt", "e.txt"); } - /** - * Method description - * - * - * @return - */ - private GitBrowseCommand createCommand() - { + private FileObject findFile(Collection foList, String name) { + return foList.stream() + .filter(f -> name.equals(f.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("file " + name + " not found")); + } + + private GitBrowseCommand createCommand() { return new GitBrowseCommand(createContext(), repository); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java index 4b52705613..32b536e69d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java @@ -33,14 +33,12 @@ package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- - import org.junit.Test; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import java.io.IOException; -import java.util.List; +import java.util.Collection; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,8 +46,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -//~--- JDK imports ------------------------------------------------------------ - /** * * @author Sebastian Sdorra @@ -68,7 +64,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { @Test public void testBrowse() throws IOException { - List foList = getRootFromTip(new BrowseCommandRequest()); + Collection foList = getRootFromTip(new BrowseCommandRequest()); FileObject a = getFileObject(foList, "a.txt"); FileObject c = getFileObject(foList, "c"); @@ -96,7 +92,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { FileObject c = result.getFile(); assertEquals("c", c.getName()); - List foList = c.getChildren(); + Collection foList = c.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -139,7 +135,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { request.setDisableLastCommit(true); - List foList = getRootFromTip(request); + Collection foList = getRootFromTip(request); FileObject a = getFileObject(foList, "a.txt"); @@ -159,7 +155,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { assertNotNull(result); FileObject root = result.getFile(); - List foList = root.getChildren(); + Collection foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -181,33 +177,22 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { * * @return */ - private FileObject getFileObject(List foList, String name) + private FileObject getFileObject(Collection foList, String name) { - FileObject a = null; - - for (FileObject f : foList) - { - if (name.equals(f.getName())) - { - a = f; - - break; - } - } - - assertNotNull(a); - - return a; + return foList.stream() + .filter(f -> name.equals(f.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("file " + name + " not found")); } - private List getRootFromTip(BrowseCommandRequest request) throws IOException { + private Collection getRootFromTip(BrowseCommandRequest request) throws IOException { BrowserResult result = new HgBrowseCommand(cmdContext, repository).getBrowserResult(request); assertNotNull(result); FileObject root = result.getFile(); - List foList = root.getChildren(); + Collection foList = root.getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java index 014f02bdbe..bcf5d2ec55 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java @@ -33,15 +33,13 @@ package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- - import org.junit.Test; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; -import java.util.List; +import java.util.Collection; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -49,8 +47,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -//~--- JDK imports ------------------------------------------------------------ - /** * * @author Sebastian Sdorra @@ -70,7 +66,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase @Test public void testBrowse() throws RevisionNotFoundException { - List foList = getRootFromTip(new BrowseCommandRequest()); + Collection foList = getRootFromTip(new BrowseCommandRequest()); FileObject a = getFileObject(foList, "a.txt"); FileObject c = getFileObject(foList, "c"); @@ -102,7 +98,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase assertNotNull(result); - List foList = result.getFile().getChildren(); + Collection foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -145,7 +141,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase request.setDisableLastCommit(true); - List foList = getRootFromTip(request); + Collection foList = getRootFromTip(request); FileObject a = getFileObject(foList, "a.txt"); @@ -161,7 +157,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase assertNotNull(result); - List foList = result.getFile().getChildren(); + Collection foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); @@ -195,31 +191,20 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase * * @return */ - private FileObject getFileObject(List foList, String name) + private FileObject getFileObject(Collection foList, String name) { - FileObject a = null; - - for (FileObject f : foList) - { - if (name.equals(f.getName())) - { - a = f; - - break; - } - } - - assertNotNull(a); - - return a; + return foList.stream() + .filter(f -> name.equals(f.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("file " + name + " not found")); } - private List getRootFromTip(BrowseCommandRequest request) throws RevisionNotFoundException { + private Collection getRootFromTip(BrowseCommandRequest request) throws RevisionNotFoundException { BrowserResult result = createCommand().getBrowserResult(request); assertNotNull(result); - List foList = result.getFile().getChildren(); + Collection foList = result.getFile().getChildren(); assertNotNull(foList); assertFalse(foList.isEmpty()); From 7845443d62d3acd0c22cfab3585ef6da3a697205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 11 Oct 2018 16:32:06 +0200 Subject: [PATCH 52/96] Naming things.... --- .../resources/sonia/scm/hg/ext/fileview.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py index 439d5e4aa5..6aa9bac2f8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py @@ -47,6 +47,11 @@ class File_Collector: self.recursive = recursive self.structure = defaultdict(dict, ((FILE_MARKER, []),)) + def collect(self, paths, path = "", dir_only = False): + for p in paths: + if p.startswith(path): + self.attach(self.extract_name_without_parent(path, p), self.structure, dir_only) + def attach(self, branch, trunk, dir_only = False): parts = branch.split('/', 1) if len(parts) == 1: # branch is a file @@ -61,18 +66,13 @@ class File_Collector: if self.recursive: self.attach(others, trunk[node], dir_only) - def create_path(self, parent, path): + def extract_name_without_parent(self, parent, name_with_parent): if len(parent) > 0: - newPath = path[len(parent):] - if newPath.startswith("/"): - newPath = newPath[1:] - return newPath - return path - - def collect(self, paths, path = "", dir_only = False): - for p in paths: - if p.startswith(path): - self.attach(self.create_path(path, p), self.structure, dir_only) + name_without_parent = name_with_parent[len(parent):] + if name_without_parent.startswith("/"): + name_without_parent = name_without_parent[1:] + return name_without_parent + return name_with_parent class File_Object: def __init__(self, directory, path): From 2e283ef4b8e9f349584f1d2da9d309223752967c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 16 Oct 2018 14:52:01 +0200 Subject: [PATCH 53/96] Do not render empty children list This looks awkward for files. --- .../main/java/sonia/scm/api/v2/resources/FileObjectDto.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index e42918e967..a9d67c2cf3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -32,6 +32,9 @@ public class FileObjectDto extends HalRepresentation { } public void setChildren(List children) { - this.withEmbedded("children", children); + if (!children.isEmpty()) { + // prevent empty embedded attribute in json + this.withEmbedded("children", children); + } } } From fb6e93e18adccf24de7571051969fe7f10ee69e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 16 Oct 2018 14:52:54 +0200 Subject: [PATCH 54/96] Let mapstruct handle mapping of children collection --- .../api/v2/resources/FileObjectToFileObjectDtoMapper.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 441e111311..7ba8d21c75 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -40,13 +40,6 @@ public abstract class FileObjectToFileObjectDtoMapper implements InstantAttribut } dto.add(links.build()); - if (fileObject.isDirectory() && fileObject.hasChildren()) { - List children = fileObject.getChildren() - .stream() - .map(fo -> map(fo, namespaceAndName, revision)) - .collect(Collectors.toList()); - dto.setChildren(children); - } } private String removeFirstSlash(String source) { From 03684f7794c44d49c1ec5b59e7b035b4c5cedec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 16 Oct 2018 15:51:44 +0200 Subject: [PATCH 55/96] Do not render empty attributes --- .../src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index a9d67c2cf3..c183d731c6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -17,10 +17,12 @@ public class FileObjectDto extends HalRepresentation { private String name; private String path; private boolean directory; + @JsonInclude(JsonInclude.Include.NON_EMPTY) private String description; private long length; @JsonInclude(JsonInclude.Include.NON_EMPTY) private Instant lastModified; + @JsonInclude(JsonInclude.Include.NON_EMPTY) private SubRepositoryDto subRepository; @JsonInclude(JsonInclude.Include.NON_EMPTY) private String revision; From fff20b973f6e7feea5ab09f1d4ab6cfdb134f1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 16 Oct 2018 17:00:12 +0200 Subject: [PATCH 56/96] Format file --- .../src/main/java/sonia/scm/repository/spi/BrowseCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java index 74cf91997d..be679f9df1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java @@ -60,4 +60,5 @@ public interface BrowseCommand * * @throws IOException */ - BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, NotFoundException;} + BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, NotFoundException; +} From a67d26575a4c635b30da307d01d95a6b760af30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 16 Oct 2018 17:01:48 +0200 Subject: [PATCH 57/96] Revert debug output --- .../test/java/sonia/scm/it/utils/ScmRequests.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 70e0d2a896..65f8e42ca0 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -77,15 +77,10 @@ public class ScmRequests { * @return the response of the GET request using the given url */ private Response applyGETRequestWithQueryParams(String url, String params) { - try { - return RestAssured.given() - .auth().preemptive().basic(username, password) - .when() - .get(url + params); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); - } + return RestAssured.given() + .auth().preemptive().basic(username, password) + .when() + .get(url + params); } /** From 8c46a7f7140bf966057e103f4d85555afbf45d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 17 Oct 2018 08:05:29 +0200 Subject: [PATCH 58/96] Name things --- .../scm/repository/spi/GitBrowseCommand.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index df40efaea4..ab1b0ae420 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -38,7 +38,6 @@ package sonia.scm.repository.spi; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; @@ -282,15 +281,14 @@ public class GitBrowseCommand extends AbstractGitCommand } else { - // TODO throw exception - logger.error("could not find tree for {}", revId.name()); + throw new IllegalStateException("could not find tree for " + revId.name()); } if (isRootRequest(request)) { result = createEmtpyRoot(); findChildren(result, repo, request, revId, treeWalk); } else { - result = first(repo, request, revId, treeWalk); + result = findFirstMatch(repo, request, revId, treeWalk); if ( result.isDirectory() ) { treeWalk.enterSubtree(); findChildren(result, repo, request, revId, treeWalk); @@ -338,19 +336,19 @@ public class GitBrowseCommand extends AbstractGitCommand return null; } - private FileObject first(org.eclipse.jgit.lib.Repository repo, + private FileObject findFirstMatch(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, NotFoundException { - String[] parts = request.getPath().split("/"); - int current = 0; - int limit = parts.length; + String[] pathElements = request.getPath().split("/"); + int currentDepth = 0; + int limit = pathElements.length; while (treeWalk.next()) { String name = treeWalk.getNameString(); - if (name.equalsIgnoreCase(parts[current])) { - current++; + if (name.equalsIgnoreCase(pathElements[currentDepth])) { + currentDepth++; - if (current >= limit) { + if (currentDepth >= limit) { return createFileObject(repo, request, revId, treeWalk); } else { treeWalk.enterSubtree(); From 04432f6a505ccdbea4ad81c1cf4d3cc1fc4814dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 17 Oct 2018 15:34:22 +0200 Subject: [PATCH 59/96] Show size for empty files, but not for directories --- scm-ui/src/repos/sources/components/FileSize.js | 4 ++-- scm-ui/src/repos/sources/components/FileSize.test.js | 3 ++- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileSize.js b/scm-ui/src/repos/sources/components/FileSize.js index 1f613ad9fc..d45157237d 100644 --- a/scm-ui/src/repos/sources/components/FileSize.js +++ b/scm-ui/src/repos/sources/components/FileSize.js @@ -6,9 +6,9 @@ type Props = { }; class FileSize extends React.Component { - static format(bytes) { + static format(bytes: number) { if (!bytes) { - return ""; + return "0 B"; } const units = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"]; diff --git a/scm-ui/src/repos/sources/components/FileSize.test.js b/scm-ui/src/repos/sources/components/FileSize.test.js index 1bbe511eb1..8ecb53e1bb 100644 --- a/scm-ui/src/repos/sources/components/FileSize.test.js +++ b/scm-ui/src/repos/sources/components/FileSize.test.js @@ -1,7 +1,8 @@ import FileSize from "./FileSize"; it("should format bytes", () => { - expect(FileSize.format(160)).toBe("160.00 B"); + expect(FileSize.format(0)).toBe("0 B"); + expect(FileSize.format(160)).toBe("160 B"); expect(FileSize.format(6304)).toBe("6.16 K"); expect(FileSize.format(28792588)).toBe("27.46 M"); expect(FileSize.format(1369510189)).toBe("1.28 G"); diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 7751f4dff9..5224ba3ded 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -62,13 +62,13 @@ class FileTreeLeaf extends React.Component { render() { const { file, classes } = this.props; + const fileSize = file.directory ? "" : ; + return ( - + From 86e85fbac0837e6df09bc8f8d5ac0a7ae1fbafa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 17 Oct 2018 16:01:40 +0200 Subject: [PATCH 60/96] Sort files directories first and by name --- .../packages/ui-types/src/Sources.js | 1 + .../src/repos/sources/components/FileTree.js | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-types/src/Sources.js b/scm-ui-components/packages/ui-types/src/Sources.js index 3852466635..c8b3fafe0c 100644 --- a/scm-ui-components/packages/ui-types/src/Sources.js +++ b/scm-ui-components/packages/ui-types/src/Sources.js @@ -14,6 +14,7 @@ export type File = { path: string, directory: boolean, description?: string, + revision: string, length: number, lastModified?: string, subRepository?: SubRepository, // TODO diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index a2c0445ba8..1ac98faa5e 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -45,6 +45,22 @@ class FileTree extends React.Component { baseUrlWithRevision += "/" + tree.revision; } + const compareFiles = function(f1: File, f2: File): number { + if (f1.directory) { + if (f2.directory) { + return f1.name.localeCompare(f2.name); + } else { + return -1; + } + } else { + if (f2.directory) { + return 1; + } else { + return f1.name.localeCompare(f2.name); + } + } + }; + const files = []; if (path) { files.push({ @@ -53,7 +69,8 @@ class FileTree extends React.Component { directory: true }); } - files.push(...tree._embedded.children); + + files.push(...tree._embedded.children.sort(compareFiles)); return (
    {this.createFileIcon(file)} {this.createFileName(file)} - - {fileSize}
    @@ -79,4 +96,5 @@ class FileTree extends React.Component { ); } } + export default injectSheet(styles)(translate("repos")(FileTree)); From 978af39c430b75085110be5470baf1ca055463d9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 18 Oct 2018 11:13:14 +0200 Subject: [PATCH 61/96] fixed changeset urls without slash at the end --- .../packages/ui-components/src/urls.js | 15 ++++++++ .../packages/ui-components/src/urls.test.js | 24 ++++++++++++- scm-ui/src/repos/modules/changesets.js | 36 +++++-------------- scm-ui/src/repos/modules/changesets.test.js | 30 +++++++--------- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/urls.js b/scm-ui-components/packages/ui-components/src/urls.js index 543519f952..3c33265eae 100644 --- a/scm-ui-components/packages/ui-components/src/urls.js +++ b/scm-ui-components/packages/ui-components/src/urls.js @@ -5,6 +5,21 @@ export function withContextPath(path: string) { return contextPath + path; } +export function withEndingSlash(url: string) { + if (url.endsWith("/")) { + return url; + } + return url + "/"; +} + +export function concat(base: string, ...parts: string) { + let url = base; + for ( let p of parts) { + url = withEndingSlash(url) + p; + } + return url; +} + export function getPageFromMatch(match: any) { let page = parseInt(match.params.page, 10); if (isNaN(page) || !page) { diff --git a/scm-ui-components/packages/ui-components/src/urls.test.js b/scm-ui-components/packages/ui-components/src/urls.test.js index 61803f213f..e1d88bfe55 100644 --- a/scm-ui-components/packages/ui-components/src/urls.test.js +++ b/scm-ui-components/packages/ui-components/src/urls.test.js @@ -1,5 +1,27 @@ // @flow -import { getPageFromMatch } from "./urls"; +import { concat, getPageFromMatch, withEndingSlash } from "./urls"; + +describe("tests for withEndingSlash", () => { + + it("should append missing slash", () => { + expect(withEndingSlash("abc")).toBe("abc/"); + }); + + it("should not append a second slash", () => { + expect(withEndingSlash("abc/")).toBe("abc/"); + }); + +}); + +describe("concat tests", () => { + + it("should concat the parts to a single url", () => { + expect(concat("a")).toBe("a"); + expect(concat("a", "b")).toBe("a/b"); + expect(concat("a", "b", "c")).toBe("a/b/c"); + }); + +}); describe("tests for getPageFromMatch", () => { function createMatch(page: string) { diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index ec8f108546..928b760bf2 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -5,7 +5,7 @@ import { PENDING_SUFFIX, SUCCESS_SUFFIX } from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; +import { apiClient, urls } from "@scm-manager/ui-components"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import type { @@ -20,20 +20,14 @@ export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; -//********added for detailed view of changesets - export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET"; export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`; export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`; -//********end of detailed view add - // actions //TODO: Content type -//********added for detailed view of changesets - export function fetchChangesetIfNeeded(repository: Repository, id: string) { return (dispatch: any, getState: any) => { if (shouldFetchChangeset(getState(), repository, id)) { @@ -46,7 +40,7 @@ export function fetchChangeset(repository: Repository, id: string) { return function(dispatch: any) { dispatch(fetchChangesetPending(repository, id)); return apiClient - .get(repository._links.changesets.href + id) + .get(createChangesetUrl(repository, id)) .then(response => response.json()) .then(data => dispatch(fetchChangesetSuccess(data, repository, id))) .catch(err => { @@ -55,6 +49,10 @@ export function fetchChangeset(repository: Repository, id: string) { }; } +function createChangesetUrl(repository: Repository, id: string) { + return urls.concat(repository._links.changesets.href, id); +} + export function fetchChangesetPending( repository: Repository, id: string @@ -93,8 +91,6 @@ function fetchChangesetFailure( }; } -//********end of detailed view add - export function fetchChangesets( repository: Repository, branch?: Branch, @@ -266,21 +262,6 @@ function extractChangesetsByIds(changesets: any) { return changesetsByIds; } -//********added for detailed view of changesets - -function addChangesetToChangesets(data: any, oldChangesetsByIds: any) { - const changeset = data; - const changesetsByIds = {}; - - changesetsByIds[changeset.id] = changeset; - - for (let id in oldChangesetsByIds) { - changesetsByIds[id] = oldChangesetsByIds[id]; - } - - return changesetsByIds; -} -//********end of added for detailed view of changesets //selectors export function getChangesets( @@ -291,15 +272,15 @@ export function getChangesets( const key = createItemId(repository, branch); const changesets = state.changesets[key]; - if (!changesets) { + if (!changesets || !changesets.list) { return null; } + return changesets.list.entries.map((id: string) => { return changesets.byId[id]; }); } -//********added for detailed view of changesets export function getChangeset( state: Object, repository: Repository, @@ -350,7 +331,6 @@ export function getFetchChangesetFailure( createChangesetItemId(repository, id) ); } -//********end of added for detailed view of changesets export function isFetchChangesetsPending( state: Object, diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 27adbdb235..8d1e2e3909 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -152,12 +152,10 @@ describe("changesets", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } @@ -427,12 +425,10 @@ describe("changesets", () => { it("should return changeset", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } @@ -478,12 +474,10 @@ describe("changesets", () => { it("should return false if changeset exists", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } From 167e8dbdca420d56d4decb38e831c4d22c073270 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 18 Oct 2018 11:55:05 +0200 Subject: [PATCH 62/96] do not store changeset with their branch --- scm-ui/src/repos/modules/changesets.js | 65 ++++++++---- scm-ui/src/repos/modules/changesets.test.js | 107 +++++++++++++++----- 2 files changed, 125 insertions(+), 47 deletions(-) diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 928b760bf2..3cd617ac56 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -148,7 +148,11 @@ export function fetchChangesetsSuccess( ): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: changesets, + payload: { + repository, + branch, + changesets + }, itemId: createItemId(repository, branch) }; } @@ -216,7 +220,7 @@ export default function reducer( }; case FETCH_CHANGESETS_SUCCESS: - const changesets = payload._embedded.changesets; + const changesets = payload.changesets._embedded.changesets; const changesetIds = changesets.map(c => c.id); const key = action.itemId; @@ -224,26 +228,32 @@ export default function reducer( return state; } - let oldByIds = {}; - if (state[key] && state[key].byId) { - oldByIds = state[key].byId; + const repoId = createItemId(payload.repository); + + let oldState = {}; + if (state[repoId]) { + oldState = state[repoId]; } + const branchName = payload.branch ? payload.branch.name : ""; const byIds = extractChangesetsByIds(changesets); return { ...state, - [key]: { + [repoId]: { byId: { - ...oldByIds, + ...oldState.byId, ...byIds }, - list: { - entries: changesetIds, - entry: { - page: payload.page, - pageTotal: payload.pageTotal, - _links: payload._links + byBranch: { + ...oldState.byBranch, + [branchName]: { + entries: changesetIds, + entry: { + page: payload.changesets.page, + pageTotal: payload.changesets.pageTotal, + _links: payload.changesets._links + } } } } @@ -269,15 +279,22 @@ export function getChangesets( repository: Repository, branch?: Branch ) { - const key = createItemId(repository, branch); + const repoKey = createItemId(repository); - const changesets = state.changesets[key]; - if (!changesets || !changesets.list) { + const stateRoot = state.changesets[repoKey]; + if (!stateRoot || !stateRoot.byBranch) { return null; } - return changesets.list.entries.map((id: string) => { - return changesets.byId[id]; + const branchName = branch ? branch.name : ""; + + const changesets = stateRoot.byBranch[branchName]; + if (!changesets) { + return null; + } + + return changesets.entries.map((id: string) => { + return stateRoot.byId[id]; }); } @@ -349,9 +366,15 @@ export function getFetchChangesetsFailure( } const selectList = (state: Object, repository: Repository, branch?: Branch) => { - const itemId = createItemId(repository, branch); - if (state.changesets[itemId] && state.changesets[itemId].list) { - return state.changesets[itemId].list; + const repoId = createItemId(repository); + + const branchName = branch ? branch.name : ""; + if (state.changesets[repoId]) { + const repoState = state.changesets[repoId]; + + if (repoState.byBranch && repoState.byBranch[branchName]) { + return repoState.byBranch[branchName]; + } } return {}; }; diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 8d1e2e3909..351c31baad 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -23,7 +23,8 @@ import reducer, { shouldFetchChangeset, isFetchChangesetPending, getFetchChangesetFailure, - fetchChangesetSuccess + fetchChangesetSuccess, + selectListAsCollection } from "./changesets"; const branch = { @@ -177,7 +178,11 @@ describe("changesets", () => { }, { type: FETCH_CHANGESETS_SUCCESS, - payload: changesets, + payload: { + repository, + undefined, + changesets + }, itemId: "foo/bar" } ]; @@ -199,7 +204,11 @@ describe("changesets", () => { }, { type: FETCH_CHANGESETS_SUCCESS, - payload: changesets, + payload: { + repository, + branch, + changesets + }, itemId } ]; @@ -258,7 +267,11 @@ describe("changesets", () => { }, { type: FETCH_CHANGESETS_SUCCESS, - payload: changesets, + payload: { + repository, + undefined, + changesets + }, itemId: "foo/bar" } ]; @@ -281,7 +294,11 @@ describe("changesets", () => { }, { type: FETCH_CHANGESETS_SUCCESS, - payload: changesets, + payload: { + repository, + branch, + changesets + }, itemId: "foo/bar/specific" } ]; @@ -323,7 +340,7 @@ describe("changesets", () => { ); expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); - expect(newState["foo/bar"].list).toEqual({ + expect(newState["foo/bar"].byBranch[""]).toEqual({ entry: { page: 1, pageTotal: 10, @@ -333,6 +350,20 @@ describe("changesets", () => { }); }); + it("should store the changeset list to branch", () => { + const newState = reducer( + {}, + fetchChangesetsSuccess(repository, branch, responseBody) + ); + + expect(newState["foo/bar"].byId["changeset1"]).toBeDefined(); + expect(newState["foo/bar"].byBranch["specific"].entries).toEqual([ + "changeset1", + "changeset2", + "changeset3" + ]); + }); + it("should not remove existing changesets", () => { const state = { "foo/bar": { @@ -340,8 +371,10 @@ describe("changesets", () => { id2: { id: "id2" }, id1: { id: "id1" } }, - list: { - entries: ["id1", "id2"] + byBranch: { + "": { + entries: ["id1", "id2"] + } } } }; @@ -353,7 +386,7 @@ describe("changesets", () => { const fooBar = newState["foo/bar"]; - expect(fooBar.list.entries).toEqual([ + expect(fooBar.byBranch[""].entries).toEqual([ "changeset1", "changeset2", "changeset3" @@ -362,7 +395,6 @@ describe("changesets", () => { expect(fooBar.byId["id1"]).toEqual({ id: "id1" }); }); - //********added for detailed view of changesets const responseBodySingleChangeset = { id: "id3", author: { @@ -440,12 +472,10 @@ describe("changesets", () => { it("should return null if changeset does not exist", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } @@ -457,12 +487,10 @@ describe("changesets", () => { it("should return true if changeset does not exist", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } @@ -514,8 +542,6 @@ describe("changesets", () => { expect(getFetchChangesetFailure({}, repository, "id1")).toBeUndefined(); }); - //********end of added for detailed view of changesets - it("should get all changesets for a given repository", () => { const state = { changesets: { @@ -524,8 +550,10 @@ describe("changesets", () => { id2: { id: "id2" }, id1: { id: "id1" } }, - list: { - entries: ["id1", "id2"] + byBranch: { + "": { + entries: ["id1", "id2"] + } } } } @@ -561,5 +589,32 @@ describe("changesets", () => { it("should return false if fetching changesets did not fail", () => { expect(getFetchChangesetsFailure({}, repository)).toBeUndefined(); }); + + it("should return list as collection for the default branch", () => { + const state = { + changesets: { + "foo/bar": { + byId: { + id2: { id: "id2" }, + id1: { id: "id1" } + }, + byBranch: { + "": { + entry: { + page: 1, + pageTotal: 10, + _links: {} + }, + entries: ["id1", "id2"] + } + } + } + } + }; + + const collection = selectListAsCollection(state, repository); + expect(collection.page).toBe(1); + expect(collection.pageTotal).toBe(10); + }); }); }); From 9ac5b3fe37c757897384462e096cbda76422f00c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 18 Oct 2018 14:40:35 +0200 Subject: [PATCH 63/96] improve changeset styling --- .../packages/ui-components/src/Image.js | 13 ++- scm-ui/public/locales/en/repos.json | 2 +- .../src/repos/components/ChangesetAvatar.js | 26 ----- .../src/repos/components/ChangesetDetails.js | 69 ------------- .../components/changesets/AvatarImage.js | 32 +++++++ .../components/changesets/AvatarWrapper.js | 18 ++++ .../components/changesets/ChangesetAvatar.js | 30 ------ .../components/changesets/ChangesetDetails.js | 96 +++++++++++++++++++ .../components/changesets/ChangesetId.js | 30 +++++- .../components/changesets/ChangesetRow.js | 17 +++- .../repos/components/changesets/changesets.js | 24 +++++ .../components/changesets/changesets.test.js | 16 ++++ scm-ui/src/repos/containers/ChangesetView.js | 2 +- 13 files changed, 239 insertions(+), 136 deletions(-) delete mode 100644 scm-ui/src/repos/components/ChangesetAvatar.js delete mode 100644 scm-ui/src/repos/components/ChangesetDetails.js create mode 100644 scm-ui/src/repos/components/changesets/AvatarImage.js create mode 100644 scm-ui/src/repos/components/changesets/AvatarWrapper.js delete mode 100644 scm-ui/src/repos/components/changesets/ChangesetAvatar.js create mode 100644 scm-ui/src/repos/components/changesets/ChangesetDetails.js create mode 100644 scm-ui/src/repos/components/changesets/changesets.js create mode 100644 scm-ui/src/repos/components/changesets/changesets.test.js diff --git a/scm-ui-components/packages/ui-components/src/Image.js b/scm-ui-components/packages/ui-components/src/Image.js index d46a32217f..5cb7fd6aa9 100644 --- a/scm-ui-components/packages/ui-components/src/Image.js +++ b/scm-ui-components/packages/ui-components/src/Image.js @@ -9,9 +9,18 @@ type Props = { }; class Image extends React.Component { + + createImageSrc = () => { + const { src } = this.props; + if (src.startsWith("http")) { + return src; + } + return withContextPath(src); + }; + render() { - const { src, alt, className } = this.props; - return {alt}; + const { alt, className } = this.props; + return {alt}; } } diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 28c94f63e3..c9dcc84815 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -53,7 +53,7 @@ "description": "Description", "contact": "Contact", "date": "Date", - "summary": "Changeset {{id}} committed {{time}}" + "summary": "Changeset {{id}} was committed {{time}}" }, "author": { "name": "Author", diff --git a/scm-ui/src/repos/components/ChangesetAvatar.js b/scm-ui/src/repos/components/ChangesetAvatar.js deleted file mode 100644 index a23b58aed9..0000000000 --- a/scm-ui/src/repos/components/ChangesetAvatar.js +++ /dev/null @@ -1,26 +0,0 @@ -//@flow -import React from "react"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; -import { Image } from "@scm-manager/ui-components"; - -type Props = { - changeset: Changeset, - repository: Repository -}; - -class ChangesetAvatar extends React.Component { - render() { - const { changeset, repository } = this.props; - return ( -

    - -

    - ); - } -} - -export default ChangesetAvatar; diff --git a/scm-ui/src/repos/components/ChangesetDetails.js b/scm-ui/src/repos/components/ChangesetDetails.js deleted file mode 100644 index 019762d140..0000000000 --- a/scm-ui/src/repos/components/ChangesetDetails.js +++ /dev/null @@ -1,69 +0,0 @@ -//@flow -import React from "react"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import { MailLink, DateFromNow } from "@scm-manager/ui-components"; -import ChangesetAvatar from "./ChangesetAvatar"; -import classNames from "classnames"; -import injectSheet from "react-jss"; - -const styles = { - floatLeft: { - float: "left" - } -}; - -type Props = { - changeset: Changeset, - repository: Repository, - t: string => string, - classes: any -}; - -class ChangesetDetails extends React.Component { - render() { - const { changeset, repository, t, classes } = this.props; - - const mailadress = changeset.author.mail ? ( -
    - - - - ) : null; - - return ( -
    -
    {t("author.mail")} - -
    - - - - - - - - - - {mailadress} - - - - - - - - - -
    {t("changeset.id")}{changeset.id}
    {t("author.name")}{changeset.author.name}
    {t("changeset.description")}{changeset.description}
    {t("changeset.date")} - -
    -
    - -
    -
    - ); - } -} - -export default injectSheet(styles)(translate("changesets")(ChangesetDetails)); diff --git a/scm-ui/src/repos/components/changesets/AvatarImage.js b/scm-ui/src/repos/components/changesets/AvatarImage.js new file mode 100644 index 0000000000..77792b1690 --- /dev/null +++ b/scm-ui/src/repos/components/changesets/AvatarImage.js @@ -0,0 +1,32 @@ +//@flow +import React from "react"; +import { binder } from "@scm-manager/ui-extensions"; +import type { Changeset } from "@scm-manager/ui-types"; +import { Image } from "@scm-manager/ui-components"; + +type Props = { + changeset: Changeset +}; + +class AvatarImage extends React.Component { + render() { + const { changeset } = this.props; + + const avatarFactory = binder.getExtension("changeset.avatar-factory"); + if (avatarFactory) { + const avatar = avatarFactory(changeset); + + return ( + {changeset.author.name} + ); + } + + return null; + } +} + +export default AvatarImage; diff --git a/scm-ui/src/repos/components/changesets/AvatarWrapper.js b/scm-ui/src/repos/components/changesets/AvatarWrapper.js new file mode 100644 index 0000000000..c5274a5976 --- /dev/null +++ b/scm-ui/src/repos/components/changesets/AvatarWrapper.js @@ -0,0 +1,18 @@ +//@flow +import React from "react"; +import { binder } from "@scm-manager/ui-extensions"; + +type Props = { + children: React.Node +}; + +class AvatarWrapper extends React.Component { + render() { + if (binder.hasExtension("changeset.avatar-factory")) { + return <>{this.props.children}; + } + return null; + } +} + +export default AvatarWrapper; diff --git a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js deleted file mode 100644 index 90f116daed..0000000000 --- a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js +++ /dev/null @@ -1,30 +0,0 @@ -//@flow -import React from "react"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Changeset } from "@scm-manager/ui-types"; - -type Props = { - changeset: Changeset -}; - -class ChangesetAvatar extends React.Component { - render() { - const { changeset } = this.props; - return ( - - {/* extension should render something like this: */} - {/*
    */} - {/*
    */} - {/* Logo */} - {/*
    */} - {/*
    */} -
    - ); - } -} - -export default ChangesetAvatar; diff --git a/scm-ui/src/repos/components/changesets/ChangesetDetails.js b/scm-ui/src/repos/components/changesets/ChangesetDetails.js new file mode 100644 index 0000000000..fd1c475447 --- /dev/null +++ b/scm-ui/src/repos/components/changesets/ChangesetDetails.js @@ -0,0 +1,96 @@ +//@flow +import React from "react"; +import type { Changeset, Repository } from "../../../../../scm-ui-components/packages/ui-types/src/index"; +import { Interpolate, translate } from "react-i18next"; +import injectSheet from "react-jss"; +import ChangesetTag from "./ChangesetTag"; +import ChangesetAuthor from "./ChangesetAuthor"; +import { parseDescription } from "./changesets"; +import { DateFromNow } from "../../../../../scm-ui-components/packages/ui-components/src/index"; +import AvatarWrapper from "./AvatarWrapper"; +import AvatarImage from "./AvatarImage"; +import classNames from "classnames"; +import ChangesetId from "./ChangesetId"; + +const styles = { + spacing: { + marginRight: "1em" + } +}; + +type Props = { + changeset: Changeset, + repository: Repository, + t: string => string, + classes: any +}; + +class ChangesetDetails extends React.Component { + render() { + const { changeset, repository, t, classes } = this.props; + + const description = parseDescription(changeset.description); + + const id = ( + + ); + const date = ; + + return ( +
    +

    {description.title}

    +
    + +

    + +

    +
    +
    +

    + +

    +

    + +

    +
    +
    {this.renderTags()}
    +
    +

    + {description.message.split("\n").map((item, key) => { + return ( + + {item} +
    +
    + ); + })} +

    +
    + ); + } + + getTags = () => { + const { changeset } = this.props; + return changeset._embedded.tags || []; + }; + + renderTags = () => { + const tags = this.getTags(); + if (tags.length > 0) { + return ( +
    + {tags.map((tag: Tag) => { + return ; + })} +
    + ); + } + return null; + }; +} + +export default injectSheet(styles)(translate("repos")(ChangesetDetails)); diff --git a/scm-ui/src/repos/components/changesets/ChangesetId.js b/scm-ui/src/repos/components/changesets/ChangesetId.js index 7669cd606e..ba38e6179c 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetId.js +++ b/scm-ui/src/repos/components/changesets/ChangesetId.js @@ -6,20 +6,42 @@ import type { Repository, Changeset } from "@scm-manager/ui-types"; type Props = { repository: Repository, - changeset: Changeset + changeset: Changeset, + link: boolean }; export default class ChangesetId extends React.Component { - render() { - const { repository, changeset } = this.props; + static defaultProps = { + link: true + }; + + shortId = (changeset: Changeset) => { + return changeset.id.substr(0, 7); + }; + + renderLink = () => { + const { changeset, repository } = this.props; return ( - {changeset.id.substr(0, 7)} + {this.shortId(changeset)} ); + }; + + renderText = () => { + const { changeset } = this.props; + return this.shortId(changeset); + }; + + render() { + const { link } = this.props; + if (link) { + return this.renderLink(); + } + return this.renderText(); } } diff --git a/scm-ui/src/repos/components/changesets/ChangesetRow.js b/scm-ui/src/repos/components/changesets/ChangesetRow.js index a1a497ad67..ffe2a7eda4 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetRow.js +++ b/scm-ui/src/repos/components/changesets/ChangesetRow.js @@ -3,13 +3,15 @@ import React from "react"; import type { Changeset, Repository, Tag } from "@scm-manager/ui-types"; import classNames from "classnames"; import { translate, Interpolate } from "react-i18next"; -import ChangesetAvatar from "./ChangesetAvatar"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; import { DateFromNow } from "@scm-manager/ui-components"; import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetTag from "./ChangesetTag"; import { compose } from "redux"; +import { parseDescription } from "./changesets"; +import AvatarWrapper from "./AvatarWrapper"; +import AvatarImage from "./AvatarImage"; const styles = { pointer: { @@ -46,14 +48,23 @@ class ChangesetRow extends React.Component { const changesetLink = this.createLink(changeset); const dateFromNow = ; const authorLine = ; + const description = parseDescription(changeset.description); return (
    - + +
    +
    +

    + +

    +
    +
    +

    - {changeset.description} + {description.title}
    0) { + title = description.substring(0, lineBreak); + message = description.substring(lineBreak + 1); + } else { + title = description; + } + + return { + title, + message + }; +} diff --git a/scm-ui/src/repos/components/changesets/changesets.test.js b/scm-ui/src/repos/components/changesets/changesets.test.js new file mode 100644 index 0000000000..cd3788e9fb --- /dev/null +++ b/scm-ui/src/repos/components/changesets/changesets.test.js @@ -0,0 +1,16 @@ +// @flow + +import { parseDescription } from "./changesets"; + +describe("parseDescription tests", () => { + it("should return a description with title and message", () => { + const desc = parseDescription("Hello\nTrillian"); + expect(desc.title).toBe("Hello"); + expect(desc.message).toBe("Trillian"); + }); + + it("should return a description with title and without message", () => { + const desc = parseDescription("Hello Trillian"); + expect(desc.title).toBe("Hello Trillian"); + }); +}); diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 62f0fc1eac..b164ca03ec 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -9,7 +9,7 @@ import { getFetchChangesetFailure, isFetchChangesetPending } from "../modules/changesets"; -import ChangesetDetails from "../components/ChangesetDetails"; +import ChangesetDetails from "../components/changesets/ChangesetDetails"; import { translate } from "react-i18next"; import { Loading, ErrorPage } from "@scm-manager/ui-components"; From 9e5df56ecfad55ad8453813124c608ba05296d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Thu, 18 Oct 2018 15:20:30 +0200 Subject: [PATCH 64/96] refactoring --- .../packages/ui-components/src/urls.js | 2 +- .../ui-types/src/RepositoryPermissions.js | 9 +++----- .../packages/ui-types/src/index.js | 2 +- .../components/changesets/AvatarWrapper.js | 2 +- .../components/changesets/ChangesetDetails.js | 8 +++++-- scm-ui/src/repos/modules/changesets.test.js | 2 +- .../components/CreatePermissionForm.js | 6 ++--- .../components/permissionValidation.js | 23 +++++++++++++------ .../components/permissionValidation.test.js | 6 +++-- .../permissions/containers/Permissions.js | 6 ++--- .../repos/permissions/modules/permissions.js | 8 +++---- .../permissions/modules/permissions.test.js | 4 ++-- 12 files changed, 45 insertions(+), 33 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/urls.js b/scm-ui-components/packages/ui-components/src/urls.js index 3c33265eae..dd8888d7a3 100644 --- a/scm-ui-components/packages/ui-components/src/urls.js +++ b/scm-ui-components/packages/ui-components/src/urls.js @@ -12,7 +12,7 @@ export function withEndingSlash(url: string) { return url + "/"; } -export function concat(base: string, ...parts: string) { +export function concat(base: string, ...parts: string[]) { let url = base; for ( let p of parts) { url = withEndingSlash(url) + p; diff --git a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js index ceb5fe135e..d86e499378 100644 --- a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js +++ b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js @@ -1,14 +1,11 @@ //@flow import type { Links } from "./hal"; -export type Permission = { - name: string, - type: string, - groupPermission: boolean, - _links?: Links +export type Permission = PermissionCreateEntry & { + _links: Links }; -export type PermissionEntry = { +export type PermissionCreateEntry = { name: string, type: string, groupPermission: boolean diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index ccad0e8597..98a6ead283 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -17,4 +17,4 @@ export type { Tag } from "./Tags"; export type { Config } from "./Config"; -export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions"; +export type { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions"; diff --git a/scm-ui/src/repos/components/changesets/AvatarWrapper.js b/scm-ui/src/repos/components/changesets/AvatarWrapper.js index c5274a5976..0d3d55e62a 100644 --- a/scm-ui/src/repos/components/changesets/AvatarWrapper.js +++ b/scm-ui/src/repos/components/changesets/AvatarWrapper.js @@ -1,5 +1,5 @@ //@flow -import React from "react"; +import * as React from "react"; import { binder } from "@scm-manager/ui-extensions"; type Props = { diff --git a/scm-ui/src/repos/components/changesets/ChangesetDetails.js b/scm-ui/src/repos/components/changesets/ChangesetDetails.js index fd1c475447..a8edf0365c 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetDetails.js +++ b/scm-ui/src/repos/components/changesets/ChangesetDetails.js @@ -1,6 +1,9 @@ //@flow import React from "react"; -import type { Changeset, Repository } from "../../../../../scm-ui-components/packages/ui-types/src/index"; +import type { + Changeset, + Repository +} from "../../../../../scm-ui-components/packages/ui-types/src/index"; import { Interpolate, translate } from "react-i18next"; import injectSheet from "react-jss"; import ChangesetTag from "./ChangesetTag"; @@ -11,6 +14,7 @@ import AvatarWrapper from "./AvatarWrapper"; import AvatarImage from "./AvatarImage"; import classNames from "classnames"; import ChangesetId from "./ChangesetId"; +import type { Tag } from "@scm-manager/ui-types"; const styles = { spacing: { @@ -27,7 +31,7 @@ type Props = { class ChangesetDetails extends React.Component { render() { - const { changeset, repository, t, classes } = this.props; + const { changeset, repository, classes } = this.props; const description = parseDescription(changeset.description); diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 351c31baad..489312688d 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -415,7 +415,7 @@ describe("changesets", () => { { "foo/bar": { byId: { - ["id2"]: { + "id2": { id: "id2", author: { mail: "mail@author.com", name: "author" } } diff --git a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js index 595c27d8ef..5268de7e9d 100644 --- a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js @@ -5,13 +5,13 @@ import { Checkbox, InputField, SubmitButton } from "@scm-manager/ui-components"; import TypeSelector from "./TypeSelector"; import type { PermissionCollection, - PermissionEntry + PermissionCreateEntry } from "@scm-manager/ui-types"; import * as validator from "./permissionValidation"; type Props = { t: string => string, - createPermission: (permission: PermissionEntry) => void, + createPermission: (permission: PermissionCreateEntry) => void, loading: boolean, currentPermissions: PermissionCollection }; @@ -65,7 +65,7 @@ class CreatePermissionForm extends React.Component {

    diff --git a/scm-ui/src/repos/permissions/components/permissionValidation.js b/scm-ui/src/repos/permissions/components/permissionValidation.js index b74ae40988..d63ab72d54 100644 --- a/scm-ui/src/repos/permissions/components/permissionValidation.js +++ b/scm-ui/src/repos/permissions/components/permissionValidation.js @@ -1,21 +1,30 @@ // @flow import { validation } from "@scm-manager/ui-components"; -import type { - PermissionCollection, -} from "@scm-manager/ui-types"; +import type { PermissionCollection } from "@scm-manager/ui-types"; const isNameValid = validation.isNameValid; export { isNameValid }; -export const isPermissionValid = (name: string, groupPermission: boolean, permissions: PermissionCollection) => { - return isNameValid(name) && !currentPermissionIncludeName(name, groupPermission, permissions); +export const isPermissionValid = ( + name: string, + groupPermission: boolean, + permissions: PermissionCollection +) => { + return ( + isNameValid(name) && + !currentPermissionIncludeName(name, groupPermission, permissions) + ); }; -const currentPermissionIncludeName = (name: string, groupPermission: boolean, permissions: PermissionCollection) => { +const currentPermissionIncludeName = ( + name: string, + groupPermission: boolean, + permissions: PermissionCollection +) => { for (let i = 0; i < permissions.length; i++) { if ( permissions[i].name === name && - permissions[i].groupPermission == groupPermission + permissions[i].groupPermission === groupPermission ) return true; } diff --git a/scm-ui/src/repos/permissions/components/permissionValidation.test.js b/scm-ui/src/repos/permissions/components/permissionValidation.test.js index 036375a348..b2e7e8be68 100644 --- a/scm-ui/src/repos/permissions/components/permissionValidation.test.js +++ b/scm-ui/src/repos/permissions/components/permissionValidation.test.js @@ -17,7 +17,8 @@ describe("permission validation", () => { { name: "PermissionName", groupPermission: true, - type: "READ" + type: "READ", + _links: {} } ]; const name = "PermissionName"; @@ -33,7 +34,8 @@ describe("permission validation", () => { { name: "PermissionName", groupPermission: false, - type: "READ" + type: "READ", + _links: {} } ]; const name = "PermissionName"; diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index e70e02f443..0a0b285102 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -21,7 +21,7 @@ import { Loading, ErrorPage } from "@scm-manager/ui-components"; import type { Permission, PermissionCollection, - PermissionEntry + PermissionCreateEntry } from "@scm-manager/ui-types"; import SinglePermission from "./SinglePermission"; import CreatePermissionForm from "../components/CreatePermissionForm"; @@ -39,7 +39,7 @@ type Props = { //dispatch functions fetchPermissions: (namespace: string, repoName: string) => void, createPermission: ( - permission: PermissionEntry, + permission: PermissionCreateEntry, namespace: string, repoName: string, callback?: () => void @@ -176,7 +176,7 @@ const mapDispatchToProps = dispatch => { dispatch(fetchPermissions(namespace, repoName)); }, createPermission: ( - permission: PermissionEntry, + permission: PermissionCreateEntry, namespace: string, repoName: string, callback?: () => void diff --git a/scm-ui/src/repos/permissions/modules/permissions.js b/scm-ui/src/repos/permissions/modules/permissions.js index 86d78e7ae9..35d0996cec 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.js +++ b/scm-ui/src/repos/permissions/modules/permissions.js @@ -6,7 +6,7 @@ import type { Action } from "@scm-manager/ui-components"; import type { PermissionCollection, Permission, - PermissionEntry + PermissionCreateEntry } from "@scm-manager/ui-types"; import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; @@ -219,7 +219,7 @@ export function modifyPermissionReset(namespace: string, repoName: string) { // create permission export function createPermission( - permission: PermissionEntry, + permission: PermissionCreateEntry, namespace: string, repoName: string, callback?: () => void @@ -260,7 +260,7 @@ export function createPermission( } export function createPermissionPending( - permission: PermissionEntry, + permission: PermissionCreateEntry, namespace: string, repoName: string ): Action { @@ -272,7 +272,7 @@ export function createPermissionPending( } export function createPermissionSuccess( - permission: PermissionEntry, + permission: PermissionCreateEntry, namespace: string, repoName: string ): Action { diff --git a/scm-ui/src/repos/permissions/modules/permissions.test.js b/scm-ui/src/repos/permissions/modules/permissions.test.js index e546f6cb00..084e752724 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.test.js +++ b/scm-ui/src/repos/permissions/modules/permissions.test.js @@ -640,7 +640,7 @@ describe("permissions selectors", () => { it("should return true, when createPermission is true", () => { const state = { permissions: { - ["hitchhiker/puzzle42"]: { + "hitchhiker/puzzle42": { createPermission: true } } @@ -651,7 +651,7 @@ describe("permissions selectors", () => { it("should return false, when createPermission is false", () => { const state = { permissions: { - ["hitchhiker/puzzle42"]: { + "hitchhiker/puzzle42": { createPermission: false } } From fd0a37eb5512d7b702b2d91dca2d8b143ef4f983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 09:17:26 +0200 Subject: [PATCH 65/96] Add branch selector to sources --- scm-ui/src/repos/containers/BranchSelector.js | 2 + .../src/repos/sources/containers/Sources.js | 53 ++++++++++++++++--- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchSelector.js b/scm-ui/src/repos/containers/BranchSelector.js index 2d8c817542..b5c628df12 100644 --- a/scm-ui/src/repos/containers/BranchSelector.js +++ b/scm-ui/src/repos/containers/BranchSelector.js @@ -60,6 +60,8 @@ class BranchSelector extends React.Component {
    ); + } else { + return null; } } diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index c0cb5dde54..5401aa8722 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,7 +1,7 @@ // @flow import React from "react"; import { connect } from "react-redux"; -import type { Repository, File } from "@scm-manager/ui-types"; +import type { Repository, Branch, File } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import { @@ -10,6 +10,8 @@ import { getSources, isFetchSourcesPending } from "../modules/sources"; +import BranchSelector from "../../containers/BranchSelector"; +import { getBranches } from "../../modules/branches"; type Props = { repository: Repository, @@ -19,6 +21,8 @@ type Props = { revision: string, path: string, baseUrl: string, + branches: Branch[], + selectedBranch: string, // dispatch props fetchSources: ( @@ -26,6 +30,9 @@ type Props = { revision: string, path: string ) => void, + + // Context props + history: any, match: any }; @@ -36,6 +43,16 @@ class Sources extends React.Component { fetchSources(repository, revision, path); } + branchSelected = (branch?: Branch) => { + let url; + if (branch) { + url = `${this.props.baseUrl}/${branch.revision}`; + } else { + url = `${this.props.baseUrl}/`; + } + this.props.history.push(url); + }; + render() { const { sources, revision, path, baseUrl, loading, error } = this.props; @@ -48,14 +65,32 @@ class Sources extends React.Component { } return ( - + <> + {this.renderBranchSelector()} + + ); } + + renderBranchSelector = () => { + const { repository, branches } = this.props; + if (repository._links.branches) { + return ( + { + this.branchSelected(b); + }} + /> + ); + } + return null; + }; } const mapStateToProps = (state, ownProps) => { @@ -64,6 +99,7 @@ const mapStateToProps = (state, ownProps) => { const loading = isFetchSourcesPending(state, repository, revision, path); const error = getFetchSourcesFailure(state, repository, revision, path); + const branches = getBranches(state, repository); const sources = getSources(state, repository, revision, path); return { @@ -71,7 +107,8 @@ const mapStateToProps = (state, ownProps) => { error, sources, revision, - path + path, + branches }; }; From 80976ec164568203e234b08b7fb3448f2a1086e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 10:32:01 +0200 Subject: [PATCH 66/96] Fetch branches on mount --- .../src/repos/sources/containers/Sources.js | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 5401aa8722..5c3f8b40cc 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -11,7 +11,7 @@ import { isFetchSourcesPending } from "../modules/sources"; import BranchSelector from "../../containers/BranchSelector"; -import { getBranches } from "../../modules/branches"; +import { fetchBranches, getBranches } from "../../modules/branches"; type Props = { repository: Repository, @@ -22,9 +22,9 @@ type Props = { path: string, baseUrl: string, branches: Branch[], - selectedBranch: string, // dispatch props + fetchBranches: Repository => void, fetchSources: ( repository: Repository, revision: string, @@ -38,21 +38,33 @@ type Props = { class Sources extends React.Component { componentDidMount() { - const { fetchSources, repository, revision, path } = this.props; + const { + fetchSources, + fetchBranches, + repository, + revision, + path + } = this.props; + fetchBranches(this.props.repository); fetchSources(repository, revision, path); } branchSelected = (branch?: Branch) => { let url; if (branch) { - url = `${this.props.baseUrl}/${branch.revision}`; + url = `${this.props.baseUrl}/${branch.name}`; } else { url = `${this.props.baseUrl}/`; } this.props.history.push(url); }; + findSelectedBranch = () => { + const { revision, branches } = this.props; + return branches.find((branch: Branch) => branch.name === revision); + }; + render() { const { sources, revision, path, baseUrl, loading, error } = this.props; @@ -94,8 +106,8 @@ class Sources extends React.Component { } const mapStateToProps = (state, ownProps) => { - const { repository } = ownProps; - const { revision, path } = ownProps.match.params; + const { repository, match } = ownProps; + const { revision, path } = match.params; const loading = isFetchSourcesPending(state, repository, revision, path); const error = getFetchSourcesFailure(state, repository, revision, path); @@ -116,6 +128,9 @@ const mapDispatchToProps = dispatch => { return { fetchSources: (repository: Repository, revision: string, path: string) => { dispatch(fetchSources(repository, revision, path)); + }, + fetchBranches: (repository: Repository) => { + dispatch(fetchBranches(repository)); } }; }; From b1c6036f295a27791907ff0408f899f25b5102cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 10:46:51 +0200 Subject: [PATCH 67/96] Stay in path on branch change --- scm-ui/src/repos/sources/containers/Sources.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 5c3f8b40cc..7859bc73ce 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -51,13 +51,18 @@ class Sources extends React.Component { } branchSelected = (branch?: Branch) => { + const { path, baseUrl, history } = this.props; let url; if (branch) { - url = `${this.props.baseUrl}/${branch.name}`; + if (path) { + url = `${baseUrl}/${branch.name}/${path}`; + } else { + url = `${baseUrl}/${branch.name}`; + } } else { - url = `${this.props.baseUrl}/`; + url = `${baseUrl}/`; } - this.props.history.push(url); + history.push(url); }; findSelectedBranch = () => { From 47ccc178f6f482f198334f271280703a14164f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 11:02:00 +0200 Subject: [PATCH 68/96] Simplify property access --- scm-ui/src/repos/sources/containers/Sources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 7859bc73ce..f32141a541 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -46,7 +46,7 @@ class Sources extends React.Component { path } = this.props; - fetchBranches(this.props.repository); + fetchBranches(repository); fetchSources(repository, revision, path); } From 9dc32fe12816bd69dee17967e69f8480d68da918 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Fri, 19 Oct 2018 11:15:37 +0200 Subject: [PATCH 69/96] add i18n Servlet --- .../main/resources/locales/de/plugins.json | 5 + .../main/resources/locales/en/plugins.json | 5 + .../main/resources/locales/de/plugins.json | 5 + .../main/resources/locales/en/plugins.json | 5 + .../main/resources/locales/de/plugins.json | 5 + .../main/resources/locales/en/plugins.json | 5 + .../java/sonia/scm/WebResourceServlet.java | 2 +- .../java/sonia/scm/web/i18n/I18nServlet.java | 151 ++++++++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json create mode 100644 scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json create mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json create mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json create mode 100644 scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json create mode 100644 scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json create mode 100644 scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..a405e36d23 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "git": { + "description": "die git repo ist super " + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..0412a162b3 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "git": { + "description": "the git repo is great " + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..49a3d97570 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "hg": { + "description": "die hg repo ist super " + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..28c628e81e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "hg": { + "description": "the hg repo is great " + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..1c25a5bcf5 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json @@ -0,0 +1,5 @@ +{ + "svn": { + "description": "die svn repo ist super " + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json new file mode 100644 index 0000000000..cbfbaac63b --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json @@ -0,0 +1,5 @@ +{ + "svn": { + "description": "the svn repo is great " + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java index 6b4a29961d..6405c16ddc 100644 --- a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java @@ -33,7 +33,7 @@ public class WebResourceServlet extends HttpServlet { * TODO remove old protocol servlets and hook. Move /hook/hg to api? */ @VisibleForTesting - static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/).*"; + static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/|locales/).*"; private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class); diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java new file mode 100644 index 0000000000..00d1f3aaf4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -0,0 +1,151 @@ +package sonia.scm.web.i18n; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.legman.Subscribe; +import com.google.inject.Singleton; +import com.sun.org.apache.regexp.internal.RE; +import lombok.extern.slf4j.Slf4j; +import sonia.scm.NotFoundException; +import sonia.scm.SCMContext; +import sonia.scm.Stage; +import sonia.scm.boot.RestartEvent; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.filter.WebElement; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.UberClassLoader; + +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.PrintWriter; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; + + +/** + * Collect + */ +@Singleton +@WebElement(value = I18nServlet.PATTERN, regex = true) +@Slf4j +public class I18nServlet extends HttpServlet { + + public static final String PATH = "/locales"; + public static final String PLUGINS_JSON = "plugins.json"; + public static final String PATTERN = PATH + "/[a-z\\-A-Z]*/" + PLUGINS_JSON; + public static final String CACHE_NAME = "sonia.cache.plugins.translations"; + + private final UberClassLoader uberClassLoader; + private final Cache> cache; + private RE languagePathPostfix = new RE(".*(\\-[A-Z]+)/.*"); + private ObjectMapper objectMapper = new ObjectMapper(); + + + @Inject + public I18nServlet(PluginLoader pluginLoader, CacheManager cacheManager) { + this.uberClassLoader = (UberClassLoader) pluginLoader.getUberClassLoader(); + this.cache = cacheManager.getCache(CACHE_NAME); + } + + @Subscribe + public void handleRestartEvent(RestartEvent event) { + log.info("clear cache on restart event with reason {}", event.getReason()); + cache.clear(); + } + + public Map getCollectedJson(String path, + Function>> jsonFileProvider, + BiConsumer> createdJsonFileConsumer) throws NotFoundException { + return Optional.ofNullable(jsonFileProvider.apply(path) + .orElseGet(() -> { + Optional> createdFile = collectJsonFile(path); + createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map)); + return createdFile.orElse(null); + } + )).orElseThrow(NotFoundException::new); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse response) { + try { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + String path = req.getServletPath(); + Function>> jsonFileProvider = usedPath -> Optional.empty(); + BiConsumer> createdJsonFileConsumer = (usedPath, foundJsonMap) -> log.info("A json File is created from the path {}", usedPath); + if (SCMContext.getContext().getStage() == Stage.PRODUCTION) { + log.info("In Production Stage get the plugin translations from the cache"); + jsonFileProvider = usedPath -> Optional.ofNullable( + cache.get(usedPath)); + createdJsonFileConsumer = createdJsonFileConsumer + .andThen((usedPath, map) -> log.info("Put the created json File in the cache with the key {}", usedPath)) + .andThen(cache::put); + } + out.write(objectMapper.writeValueAsString(getCollectedJson(path, jsonFileProvider, createdJsonFileConsumer))); + } catch (IOException e) { + log.error("error on getting the translation of the plugins", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (NotFoundException e) { + log.error("Plugin translations are not found", e); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } +// ScmEventBus.getInstance().post(new RestartEvent(I18nServlet.class,"einfach so")); + } + + /** + * Return a collected Json File as map with the given path from all plugins in the class path + * + * @param path the searched resource path + * @return a collected Json File as map with the given path from all plugins in the class path + */ + private Optional> collectJsonFile(String path) { + log.info("Collect plugin translations from path {} for every plugin", path); + HashMap result = null; + try { + Enumeration resources = uberClassLoader.getResources(path.replaceFirst("/", "")); + if (resources.hasMoreElements()) { + result = new HashMap<>(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + result.putAll(mergeJSONs(objectMapper, url)); + } + } + } catch (IOException e) { + log.error("Error on loading sources from {}", path, e); + } + return Optional.ofNullable(result); + } + + private boolean hasLanguagePostfix(String path) { + return languagePathPostfix.match(path); + } + + /** + * remove the -DE from the path locales/de-DE/plugins + * + * @param servletPath + * @return + * @throws IOException + */ + private String removeLanguagePostfix(String servletPath) { + return servletPath.replace(languagePathPostfix.getParen(1), ""); + } + + // TODO simplify + private HashMap mergeJSONs(ObjectMapper objectMapper, URL url) throws IOException { + byte[] src = Files.readAllBytes(Paths.get(url.getPath())); + Map json = objectMapper.readValue(src, HashMap.class); + return objectMapper.readerForUpdating(json).readValue(src); + } +} From 5a454139da16e0fa052daefe040a807ef0b295fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 13:01:06 +0200 Subject: [PATCH 70/96] Do not print bytes with decimal place --- scm-ui/src/repos/sources/components/FileSize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/components/FileSize.js b/scm-ui/src/repos/sources/components/FileSize.js index d45157237d..c7d966d901 100644 --- a/scm-ui/src/repos/sources/components/FileSize.js +++ b/scm-ui/src/repos/sources/components/FileSize.js @@ -14,7 +14,7 @@ class FileSize extends React.Component { const units = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); - const size = (bytes / 1024 ** i).toFixed(2); + const size = i === 0 ? bytes : (bytes / 1024 ** i).toFixed(2); return `${size} ${units[i]}`; } From e01a995accf8ae1ebac6d30b6e098d31ebf8064f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 13:57:49 +0200 Subject: [PATCH 71/96] Handle special characters in file names --- .../scm/api/v2/resources/ResourceLinks.java | 7 +++++- .../api/v2/resources/ResourceLinksTest.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 2b37eda9b7..1b397480a4 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -4,6 +4,7 @@ import sonia.scm.repository.NamespaceAndName; import javax.inject.Inject; import java.net.URI; +import java.net.URISyntaxException; class ResourceLinks { @@ -16,7 +17,11 @@ class ResourceLinks { // we have to add the file path using URI, so that path separators (aka '/') will not be encoded as '%2F' private static String addPath(String sourceWithPath, String path) { - return URI.create(sourceWithPath).resolve(path).toASCIIString(); + try { + return new URI(sourceWithPath).resolve(new URI(null, null, path, null)).toASCIIString(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } GroupLinks group() { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index 0544bf6a0d..74ff49dbb7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -173,6 +173,30 @@ public class ResourceLinksTest { assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url); } + @Test + public void shouldHandleSpacesInPaths() { + String url = resourceLinks.source().content("space", "name", "rev", "name with spaces"); + assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/content/rev/name%20with%20spaces", url); + } + + @Test + public void shouldHandleBackslashInPaths() { + String url = resourceLinks.source().content("space", "name", "rev", "name_with_\\"); + assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/content/rev/name_with_%5C", url); + } + + @Test + public void shouldHandleNewLineInPaths() { + String url = resourceLinks.source().content("space", "name", "rev", "name_with\nnew_line"); + assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/content/rev/name_with%0Anew_line", url); + } + + @Test + public void shouldKeepSlashesInInPaths() { + String url = resourceLinks.source().content("space", "name", "rev", "some/dir/somewhere"); + assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/content/rev/some/dir/somewhere", url); + } + @Before public void initUriInfo() { initMocks(this); From 70507ce6099f2cc367ee1f2adaea5832dba9476d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 19 Oct 2018 16:02:21 +0200 Subject: [PATCH 72/96] Load tree in FileTree --- .../src/repos/sources/components/FileTree.js | 83 +++++++++++++++++-- .../src/repos/sources/containers/Sources.js | 83 +++++++------------ 2 files changed, 105 insertions(+), 61 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 1ac98faa5e..b8fa4a0dc4 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -1,9 +1,19 @@ //@flow import React from "react"; import { translate } from "react-i18next"; +import { connect } from "react-redux"; import injectSheet from "react-jss"; import FileTreeLeaf from "./FileTreeLeaf"; -import type { File } from "@scm-manager/ui-types"; +import type { Repository, File } from "@scm-manager/ui-types"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { + fetchSources, + getFetchSourcesFailure, + isFetchSourcesPending, + getSources +} from "../modules/sources"; +import { withRouter } from "react-router-dom"; +import { compose } from "redux"; const styles = { iconColumn: { @@ -12,19 +22,23 @@ const styles = { }; type Props = { + loading: boolean, + error: Error, tree: File, + repository: Repository, revision: string, path: string, baseUrl: string, - + fetchSources: (Repository, string, string) => void, // context props classes: any, - t: string => string + t: string => string, + match: any }; export function findParent(path: string) { if (path.endsWith("/")) { - path = path.substring(path, path.length - 1); + path = path.substring(0, path.length - 1); } const index = path.lastIndexOf("/"); @@ -35,8 +49,23 @@ export function findParent(path: string) { } class FileTree extends React.Component { + componentDidMount() { + const { fetchSources, repository, revision, path } = this.props; + + fetchSources(repository, revision, path); + } + render() { - const { tree, revision, path, baseUrl, classes, t } = this.props; + const { + error, + loading, + tree, + revision, + path, + baseUrl, + classes, + t + } = this.props; let baseUrlWithRevision = baseUrl; if (revision) { @@ -61,6 +90,17 @@ class FileTree extends React.Component { } }; + if (error) { + return ; + } + + if (loading) { + return ; + } + if (!tree) { + return null; + } + const files = []; if (path) { files.push({ @@ -97,4 +137,35 @@ class FileTree extends React.Component { } } -export default injectSheet(styles)(translate("repos")(FileTree)); +const mapStateToProps = (state: any, ownProps: Props) => { + const { repository, match } = ownProps; + const { revision, path } = match.params; + + const loading = isFetchSourcesPending(state, repository, revision, path); + const error = getFetchSourcesFailure(state, repository, revision, path); + const tree = getSources(state, repository, revision, path); + + return { + loading, + error, + revision, + path, + tree + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchSources: (repository: Repository, revision: string, path: string) => { + dispatch(fetchSources(repository, revision, path)); + } + }; +}; + +export default compose( + withRouter, + connect( + mapStateToProps, + mapDispatchToProps + ) +)(injectSheet(styles)(translate("repos")(FileTree))); diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index f32141a541..2d75b4193a 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,35 +1,28 @@ // @flow import React from "react"; import { connect } from "react-redux"; +import { Route, withRouter } from "react-router-dom"; import type { Repository, Branch, File } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; -import { - fetchSources, - getFetchSourcesFailure, - getSources, - isFetchSourcesPending -} from "../modules/sources"; import BranchSelector from "../../containers/BranchSelector"; -import { fetchBranches, getBranches } from "../../modules/branches"; +import { + fetchBranches, + getBranches, + getFetchBranchesFailure, + isFetchBranchesPending +} from "../../modules/branches"; +import { compose } from "redux"; type Props = { repository: Repository, - sources: File, loading: boolean, error: Error, - revision: string, - path: string, baseUrl: string, branches: Branch[], // dispatch props fetchBranches: Repository => void, - fetchSources: ( - repository: Repository, - revision: string, - path: string - ) => void, // Context props history: any, @@ -38,57 +31,41 @@ type Props = { class Sources extends React.Component { componentDidMount() { - const { - fetchSources, - fetchBranches, - repository, - revision, - path - } = this.props; + const { fetchBranches, repository } = this.props; fetchBranches(repository); - fetchSources(repository, revision, path); } branchSelected = (branch?: Branch) => { - const { path, baseUrl, history } = this.props; + const { baseUrl, history } = this.props; let url; if (branch) { - if (path) { - url = `${baseUrl}/${branch.name}/${path}`; - } else { - url = `${baseUrl}/${branch.name}`; - } + url = `${baseUrl}/${branch.name}`; } else { url = `${baseUrl}/`; } history.push(url); }; - findSelectedBranch = () => { - const { revision, branches } = this.props; - return branches.find((branch: Branch) => branch.name === revision); - }; - render() { - const { sources, revision, path, baseUrl, loading, error } = this.props; + const { repository, baseUrl, loading, error } = this.props; if (error) { return ; } - if (!sources || loading) { + if (loading) { return ; } return ( <> {this.renderBranchSelector()} - ( + + )} /> ); @@ -111,36 +88,32 @@ class Sources extends React.Component { } const mapStateToProps = (state, ownProps) => { - const { repository, match } = ownProps; - const { revision, path } = match.params; + const { repository } = ownProps; - const loading = isFetchSourcesPending(state, repository, revision, path); - const error = getFetchSourcesFailure(state, repository, revision, path); + const loading = isFetchBranchesPending(state, repository); + const error = getFetchBranchesFailure(state, repository); const branches = getBranches(state, repository); - const sources = getSources(state, repository, revision, path); return { + repository, loading, error, - sources, - revision, - path, branches }; }; const mapDispatchToProps = dispatch => { return { - fetchSources: (repository: Repository, revision: string, path: string) => { - dispatch(fetchSources(repository, revision, path)); - }, fetchBranches: (repository: Repository) => { dispatch(fetchBranches(repository)); } }; }; -export default connect( - mapStateToProps, - mapDispatchToProps +export default compose( + withRouter, + connect( + mapStateToProps, + mapDispatchToProps + ) )(Sources); From 9cb661b4601175fe0def4840d0f39a9cc6c31e31 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Sat, 20 Oct 2018 14:40:03 +0200 Subject: [PATCH 73/96] plugin translation servlet --- .../java/sonia/scm/it/I18nServletITCase.java | 19 ++ .../java/sonia/scm/it/utils/ScmRequests.java | 10 + .../src/main/js/ProtocolInformation.js | 12 +- .../main/resources/locales/de/plugins.json | 8 +- .../main/resources/locales/en/plugins.json | 8 +- .../src/main/js/ProtocolInformation.js | 12 +- .../main/resources/locales/de/plugins.json | 8 +- .../main/resources/locales/en/plugins.json | 8 +- .../src/main/js/ProtocolInformation.js | 8 +- .../main/resources/locales/de/plugins.json | 6 +- .../main/resources/locales/en/plugins.json | 6 +- .../java/sonia/scm/web/i18n/I18nServlet.java | 68 ++--- .../sonia/scm/web/i18n/I18nServletTest.java | 253 ++++++++++++++++++ 13 files changed, 357 insertions(+), 69 deletions(-) create mode 100644 scm-it/src/test/java/sonia/scm/it/I18nServletITCase.java create mode 100644 scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java diff --git a/scm-it/src/test/java/sonia/scm/it/I18nServletITCase.java b/scm-it/src/test/java/sonia/scm/it/I18nServletITCase.java new file mode 100644 index 0000000000..6597880665 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/I18nServletITCase.java @@ -0,0 +1,19 @@ +package sonia.scm.it; + +import org.junit.Test; +import sonia.scm.it.utils.ScmRequests; + +import static org.assertj.core.api.Assertions.assertThat; + +public class I18nServletITCase { + + @Test + public void shouldGetCollectedPluginTranslations() { + ScmRequests.start() + .requestPluginTranslations("de") + .assertStatusCode(200) + .assertSingleProperty(value -> assertThat(value).isNotNull(), "scm-git-plugin") + .assertSingleProperty(value -> assertThat(value).isNotNull(), "scm-hg-plugin") + .assertSingleProperty(value -> assertThat(value).isNotNull(), "scm-svn-plugin"); + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 69c79c37bf..9d08594006 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -52,7 +52,12 @@ public class ScmRequests { setUsername(username); setPassword(password); return new ChangePasswordResponse<>(applyPUTRequest(RestUtil.REST_BASE_URL.resolve("users/"+userPathParam+"/password").toString(), VndMediaType.PASSWORD_OVERWRITE, TestData.createPasswordChangeJson(password,newPassword)), null); + } + @SuppressWarnings("unchecked") + public ModelResponse requestPluginTranslations(String language) { + Response response = applyGETRequest(RestUtil.BASE_URL.resolve("locales/" + language + "/plugins.json").toString()); + return new ModelResponse(response, null); } /** @@ -90,6 +95,11 @@ public class ScmRequests { */ private Response applyGETRequestWithQueryParams(String url, String params) { LOG.info("GET {}", url); + if (username == null || password == null){ + return RestAssured.given() + .when() + .get(url + params); + } return RestAssured.given() .auth().preemptive().basic(username, password) .when() diff --git a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js index d8eb4ae0e0..f17dc5e2e9 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js +++ b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js @@ -2,9 +2,11 @@ import React from "react"; import { repositories } from "@scm-manager/ui-components"; import type { Repository } from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; type Props = { - repository: Repository + repository: Repository, + t: string => string } class ProtocolInformation extends React.Component { @@ -18,11 +20,11 @@ class ProtocolInformation extends React.Component { return (
    -

    Clone the repository

    +

    {t("scm-git-plugin.information.clone")}

               git clone {href}
             
    -

    Create a new repository

    +

    {t("scm-git-plugin.information.create")}

               
                 git init {repository.name}
    @@ -39,7 +41,7 @@ class ProtocolInformation extends React.Component {
                 
    -

    Push an existing repository

    +

    {t("scm-git-plugin.information.replace")}

               
                 git remote add origin {href}
    @@ -54,4 +56,4 @@ class ProtocolInformation extends React.Component {
     
     }
     
    -export default ProtocolInformation;
    +export default translate("plugins")(ProtocolInformation);
    diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json
    index a405e36d23..1dc0e254c2 100644
    --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json
    +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json
    @@ -1,5 +1,9 @@
     {
    -  "git": {
    -    "description": "die git repo ist super "
    +  "scm-git-plugin": {
    +    "information": {
    +      "clone" : "Repository Klonen",
    +      "create" : "Neue Repository erstellen",
    +      "replace" : "Eine existierende Repository aktualisieren"
    +    }
       }
     }
    diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json
    index 0412a162b3..65594bae19 100644
    --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json
    +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json
    @@ -1,5 +1,9 @@
     {
    -  "git": {
    -    "description": "the git repo is great "
    +  "scm-git-plugin": {
    +    "information": {
    +      "clone" : "Clone the repository",
    +      "create" : "Create a new repository",
    +      "replace" : "Push an existing repository"
    +    }
       }
     }
    diff --git a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js
    index 03fc41450a..6a55578926 100644
    --- a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js
    +++ b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js
    @@ -2,9 +2,11 @@
     import React from "react";
     import { repositories } from "@scm-manager/ui-components";
     import type { Repository } from "@scm-manager/ui-types";
    +import { translate } from "react-i18next";
     
     type Props = {
    -  repository: Repository
    +  repository: Repository,
    +  t: string => string
     }
     
     class ProtocolInformation extends React.Component {
    @@ -17,11 +19,11 @@ class ProtocolInformation extends React.Component {
         }
         return (
           
    -

    Clone the repository

    +

    {t("scm-hg-plugin.information.clone")}

               hg clone {href}
             
    -

    Create a new repository

    +

    {t("scm-hg-plugin.information.create")}

               
                 hg init {repository.name}
    @@ -41,7 +43,7 @@ class ProtocolInformation extends React.Component {
                 
    -

    Push an existing repository

    +

    {t("scm-hg-plugin.information.replace")}

               
                 # add the repository url as default to your .hg/hgrc e.g:
    @@ -59,4 +61,4 @@ class ProtocolInformation extends React.Component {
     
     }
     
    -export default ProtocolInformation;
    +export default translate("plugins")(ProtocolInformation);
    diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json
    index 49a3d97570..0824a4ad38 100644
    --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json
    +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json
    @@ -1,5 +1,9 @@
     {
    -  "hg": {
    -    "description": "die hg repo ist super "
    +  "scm-hg-plugin": {
    +    "information": {
    +      "clone" : "Repository Klonen",
    +      "create" : "Neue Repository erstellen",
    +      "replace" : "Eine existierende Repository aktualisieren"
    +    }
       }
     }
    diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json
    index 28c628e81e..4ec1d4e4d2 100644
    --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json
    +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json
    @@ -1,5 +1,9 @@
     {
    -  "hg": {
    -    "description": "the hg repo is great "
    +  "scm-hg-plugin": {
    +    "information": {
    +      "clone" : "Clone the repository",
    +      "create" : "Create a new repository",
    +      "replace" : "Push an existing repository"
    +    }
       }
     }
    diff --git a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js
    index 0ba195887f..bcfe223b83 100644
    --- a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js
    +++ b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js
    @@ -2,9 +2,11 @@
     import React from "react";
     import { repositories } from "@scm-manager/ui-components";
     import type { Repository } from "@scm-manager/ui-types";
    +import { translate } from "react-i18next";
     
     type Props = {
    -  repository: Repository
    +  repository: Repository,
    +  t: string => string
     }
     
     class ProtocolInformation extends React.Component {
    @@ -17,7 +19,7 @@ class ProtocolInformation extends React.Component {
         }
         return (
           
    -

    Checkout the repository

    +

    {t("scm-svn-plugin.information.checkout")}

               svn checkout {href}
             
    @@ -27,4 +29,4 @@ class ProtocolInformation extends React.Component { } -export default ProtocolInformation; +export default translate("plugins")(ProtocolInformation); diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json index 1c25a5bcf5..7c58498ef1 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json @@ -1,5 +1,7 @@ { - "svn": { - "description": "die svn repo ist super " + "scm-svn-plugin": { + "information": { + "checkout" : "Repository auschecken" + } } } diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json index cbfbaac63b..07b34baf10 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json @@ -1,5 +1,7 @@ { - "svn": { - "description": "the svn repo is great " + "scm-svn-plugin": { + "information": { + "checkout" : "Checkout repository" + } } } diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index 00d1f3aaf4..d0c78bf1f5 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -4,7 +4,6 @@ package sonia.scm.web.i18n; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.legman.Subscribe; import com.google.inject.Singleton; -import com.sun.org.apache.regexp.internal.RE; import lombok.extern.slf4j.Slf4j; import sonia.scm.NotFoundException; import sonia.scm.SCMContext; @@ -34,7 +33,7 @@ import java.util.function.Function; /** - * Collect + * Collect the plugin translations. */ @Singleton @WebElement(value = I18nServlet.PATTERN, regex = true) @@ -47,9 +46,8 @@ public class I18nServlet extends HttpServlet { public static final String CACHE_NAME = "sonia.cache.plugins.translations"; private final UberClassLoader uberClassLoader; - private final Cache> cache; - private RE languagePathPostfix = new RE(".*(\\-[A-Z]+)/.*"); - private ObjectMapper objectMapper = new ObjectMapper(); + private final Cache cache; + private static ObjectMapper objectMapper = new ObjectMapper(); @Inject @@ -58,18 +56,18 @@ public class I18nServlet extends HttpServlet { this.cache = cacheManager.getCache(CACHE_NAME); } - @Subscribe + @Subscribe(async = false) public void handleRestartEvent(RestartEvent event) { - log.info("clear cache on restart event with reason {}", event.getReason()); + log.info("Clear cache on restart event with reason {}", event.getReason()); cache.clear(); } - public Map getCollectedJson(String path, - Function>> jsonFileProvider, - BiConsumer> createdJsonFileConsumer) throws NotFoundException { + private Map getCollectedJson(String path, + Function> jsonFileProvider, + BiConsumer createdJsonFileConsumer) { return Optional.ofNullable(jsonFileProvider.apply(path) .orElseGet(() -> { - Optional> createdFile = collectJsonFile(path); + Optional createdFile = collectJsonFile(path); createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map)); return createdFile.orElse(null); } @@ -82,9 +80,9 @@ public class I18nServlet extends HttpServlet { response.setContentType("application/json"); PrintWriter out = response.getWriter(); String path = req.getServletPath(); - Function>> jsonFileProvider = usedPath -> Optional.empty(); - BiConsumer> createdJsonFileConsumer = (usedPath, foundJsonMap) -> log.info("A json File is created from the path {}", usedPath); - if (SCMContext.getContext().getStage() == Stage.PRODUCTION) { + Function> jsonFileProvider = usedPath -> Optional.empty(); + BiConsumer createdJsonFileConsumer = (usedPath, foundJsonMap) -> log.info("A json File is created from the path {}", usedPath); + if (isProductionStage()) { log.info("In Production Stage get the plugin translations from the cache"); jsonFileProvider = usedPath -> Optional.ofNullable( cache.get(usedPath)); @@ -94,58 +92,40 @@ public class I18nServlet extends HttpServlet { } out.write(objectMapper.writeValueAsString(getCollectedJson(path, jsonFileProvider, createdJsonFileConsumer))); } catch (IOException e) { - log.error("error on getting the translation of the plugins", e); + log.error("Error on getting the translation of the plugins", e); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } catch (NotFoundException e) { log.error("Plugin translations are not found", e); response.setStatus(HttpServletResponse.SC_NOT_FOUND); } -// ScmEventBus.getInstance().post(new RestartEvent(I18nServlet.class,"einfach so")); + } + + protected boolean isProductionStage() { + return SCMContext.getContext().getStage() == Stage.PRODUCTION; } /** - * Return a collected Json File as map with the given path from all plugins in the class path + * Return a collected Json File as map from the given path from all plugins in the class path * * @param path the searched resource path - * @return a collected Json File as map with the given path from all plugins in the class path + * @return a collected Json File as map from the given path from all plugins in the class path */ - private Optional> collectJsonFile(String path) { + protected Optional collectJsonFile(String path) { log.info("Collect plugin translations from path {} for every plugin", path); - HashMap result = null; + Map result = null; try { Enumeration resources = uberClassLoader.getResources(path.replaceFirst("/", "")); if (resources.hasMoreElements()) { - result = new HashMap<>(); + result = new HashMap(); while (resources.hasMoreElements()) { URL url = resources.nextElement(); - result.putAll(mergeJSONs(objectMapper, url)); + result.putAll(objectMapper.readValue(Files.readAllBytes(Paths.get(url.getPath())), Map.class)); } } } catch (IOException e) { log.error("Error on loading sources from {}", path, e); + return Optional.empty(); } return Optional.ofNullable(result); } - - private boolean hasLanguagePostfix(String path) { - return languagePathPostfix.match(path); - } - - /** - * remove the -DE from the path locales/de-DE/plugins - * - * @param servletPath - * @return - * @throws IOException - */ - private String removeLanguagePostfix(String servletPath) { - return servletPath.replace(languagePathPostfix.getParen(1), ""); - } - - // TODO simplify - private HashMap mergeJSONs(ObjectMapper objectMapper, URL url) throws IOException { - byte[] src = Files.readAllBytes(Paths.get(url.getPath())); - Map json = objectMapper.readValue(src, HashMap.class); - return objectMapper.readerForUpdating(json).readValue(src); - } } diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java new file mode 100644 index 0000000000..b8deb4b069 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -0,0 +1,253 @@ +package sonia.scm.web.i18n; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import org.assertj.core.util.Lists; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockSettings; +import org.mockito.internal.creation.MockSettingsImpl; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.boot.RestartEvent; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.event.ScmEventBus; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.UberClassLoader; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") +public class I18nServletTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + private static final String GIT_PLUGIN_JSON = "{\n" + + " \"scm-git-plugin\": {\n" + + " \"information\": {\n" + + " \"clone\" : \"Clone\",\n" + + " \"create\" : \"Create\",\n" + + " \"replace\" : \"Push\"\n" + + " }\n" + + " }\n" + + "}\n"; + private static final String HG_PLUGIN_JSON = "{\n" + + " \"scm-hg-plugin\": {\n" + + " \"information\": {\n" + + " \"clone\" : \"Clone\",\n" + + " \"create\" : \"Create\",\n" + + " \"replace\" : \"Push\"\n" + + " }\n" + + " }\n" + + "}\n"; + private static String SVN_PLUGIN_JSON = "{\n" + + " \"scm-svn-plugin\": {\n" + + " \"information\": {\n" + + " \"checkout\" : \"Checkout\"\n" + + " }\n" + + " }\n" + + "}\n"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Mock + PluginLoader pluginLoader; + + @Mock + CacheManager cacheManager; + + @Mock + UberClassLoader uberClassLoader; + + I18nServlet servlet; + + @Mock + private Cache cache; + private Enumeration resources; + + @Before + @SuppressWarnings("unchecked") + public void init() throws IOException { + resources = Collections.enumeration(Lists.newArrayList( + createFileFromString(SVN_PLUGIN_JSON).toURL(), + createFileFromString(GIT_PLUGIN_JSON).toURL(), + createFileFromString(HG_PLUGIN_JSON).toURL() + )); + when(pluginLoader.getUberClassLoader()).thenReturn(uberClassLoader); + when(cacheManager.getCache(I18nServlet.CACHE_NAME)).thenReturn(cache); + MockSettings settings = new MockSettingsImpl<>(); + settings.useConstructor(pluginLoader, cacheManager); + settings.defaultAnswer(InvocationOnMock::callRealMethod); + servlet = mock(I18nServlet.class, settings); + } + + @Test + public void shouldCleanCacheOnRestartEvent() { + ScmEventBus.getInstance().register(servlet); + + ScmEventBus.getInstance().post(new RestartEvent(I18nServlet.class, "Restart to reload the plugin resources")); + + verify(cache).clear(); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFailWith404OnMissingResources() throws IOException { + String path = "/locales/de/plugins.json"; + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + PrintWriter writer = mock(PrintWriter.class); + when(response.getWriter()).thenReturn(writer); + when(request.getServletPath()).thenReturn(path); + when(uberClassLoader.getResources("locales/de/plugins.json")).thenThrow(IOException.class); + + servlet.doGet(request, response); + + verify(response).setStatus(404); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFailWith500OnIOException() throws IOException { + String path = "/locales/de/plugins.json"; + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + PrintWriter writer = mock(PrintWriter.class); + when(response.getWriter()).thenReturn(writer); + when(request.getServletPath()).thenReturn(path); + when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); + doThrow(IOException.class).when(writer).write(any(String.class)); + + servlet.doGet(request, response); + + verify(response).setStatus(500); + } + + @Test + @SuppressWarnings("unchecked") + public void inDevelopmentStageShouldNotUseCache() throws IOException { + String path = "/locales/de/plugins.json"; + when(servlet.isProductionStage()).thenReturn(false); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + PrintWriter writer = mock(PrintWriter.class); + when(response.getWriter()).thenReturn(writer); + when(request.getServletPath()).thenReturn(path); + when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + doCallRealMethod().when(writer).write(captor.capture()); + + servlet.doGet(request, response); + + assertJsonMap(jsonStringToMap(captor.getValue())); + verify(cache, never()).get(any()); + } + + @Test + @SuppressWarnings("unchecked") + public void inProductionStageShouldUseCache() throws IOException { + String path = "/locales/de/plugins.json"; + when(servlet.isProductionStage()).thenReturn(true); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + PrintWriter writer = mock(PrintWriter.class); + when(response.getWriter()).thenReturn(writer); + when(request.getServletPath()).thenReturn(path); + when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + doCallRealMethod().when(writer).write(captor.capture()); + + servlet.doGet(request, response); + + assertJsonMap(jsonStringToMap(captor.getValue())); + verify(cache).get(path); + verify(cache).put(eq(path), any()); + } + + @Test + @SuppressWarnings("unchecked") + public void inProductionStageShouldGetFromCache() throws IOException { + String path = "/locales/de/plugins.json"; + when(servlet.isProductionStage()).thenReturn(true); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + PrintWriter writer = mock(PrintWriter.class); + when(response.getWriter()).thenReturn(writer); + when(request.getServletPath()).thenReturn(path); + when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + doCallRealMethod().when(writer).write(captor.capture()); + Map cachedMap = jsonStringToMap(GIT_PLUGIN_JSON); + cachedMap.putAll(jsonStringToMap(HG_PLUGIN_JSON)); + cachedMap.putAll(jsonStringToMap(SVN_PLUGIN_JSON)); + when(cache.get(path)).thenReturn(cachedMap); + servlet.doGet(request, response); + verify(servlet, never()).collectJsonFile(path); + verify(cache, never()).put(eq(path), any()); + verify(cache).get(path); + assertJsonMap(jsonStringToMap(captor.getValue())); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldCollectJsonFile() throws IOException { + String path = "locales/de/plugins.json"; + + when(uberClassLoader.getResources(path)).thenReturn(resources); + Optional mapOptional = servlet.collectJsonFile("/" + path); + + assertJsonMap(mapOptional.orElse(null)); + } + + @SuppressWarnings("unchecked") + public void assertJsonMap(Map actual) throws IOException { + assertThat(actual) + .isNotEmpty() + .containsAllEntriesOf(jsonStringToMap(GIT_PLUGIN_JSON)) + .containsAllEntriesOf(jsonStringToMap(HG_PLUGIN_JSON)) + .containsAllEntriesOf(jsonStringToMap(SVN_PLUGIN_JSON)); + } + + public File createFileFromString(String json) throws IOException { + File file = temporaryFolder.newFile(); + Files.write(json.getBytes(Charsets.UTF_8), file); + return file; + } + + private Map jsonStringToMap(String fileAsString) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(fileAsString, Map.class); + } + +} From 728696de32ea7e20ade532a4a82c50c9ba309ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 22 Oct 2018 08:00:34 +0200 Subject: [PATCH 74/96] Render file tree even when no branch is selected --- .../src/repos/sources/components/FileTree.js | 14 +++++------ .../src/repos/sources/containers/Sources.js | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index b8fa4a0dc4..ace812e261 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -67,13 +67,6 @@ class FileTree extends React.Component { t } = this.props; - let baseUrlWithRevision = baseUrl; - if (revision) { - baseUrlWithRevision += "/" + revision; - } else { - baseUrlWithRevision += "/" + tree.revision; - } - const compareFiles = function(f1: File, f2: File): number { if (f1.directory) { if (f2.directory) { @@ -112,6 +105,13 @@ class FileTree extends React.Component { files.push(...tree._embedded.children.sort(compareFiles)); + let baseUrlWithRevision = baseUrl; + if (revision) { + baseUrlWithRevision += "/" + revision; + } else { + baseUrlWithRevision += "/" + tree.revision; + } + return ( diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 2d75b4193a..40d4e6c214 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,8 +1,8 @@ // @flow import React from "react"; import { connect } from "react-redux"; -import { Route, withRouter } from "react-router-dom"; -import type { Repository, Branch, File } from "@scm-manager/ui-types"; +import { Route, Switch, withRouter } from "react-router-dom"; +import type { Repository, Branch } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import BranchSelector from "../../containers/BranchSelector"; @@ -61,12 +61,20 @@ class Sources extends React.Component { return ( <> {this.renderBranchSelector()} - ( - - )} - /> + + ( + + )} + /> + ( + + )} + /> + ); } From 6513de47d8754d11920f519f99c84ff6b6a9ec68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 22 Oct 2018 10:23:45 +0200 Subject: [PATCH 75/96] Keep selected path when switching to other branch --- scm-ui/src/repos/sources/containers/Sources.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 40d4e6c214..9646c4b458 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -20,6 +20,7 @@ type Props = { error: Error, baseUrl: string, branches: Branch[], + path: string, // dispatch props fetchBranches: Repository => void, @@ -37,10 +38,10 @@ class Sources extends React.Component { } branchSelected = (branch?: Branch) => { - const { baseUrl, history } = this.props; + const { baseUrl, history, path } = this.props; let url; if (branch) { - url = `${baseUrl}/${branch.name}`; + url = `${baseUrl}/${branch.name}/${path}`; } else { url = `${baseUrl}/`; } @@ -96,7 +97,8 @@ class Sources extends React.Component { } const mapStateToProps = (state, ownProps) => { - const { repository } = ownProps; + const { repository, match } = ownProps; + const { path } = match.params; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); @@ -104,6 +106,7 @@ const mapStateToProps = (state, ownProps) => { return { repository, + path, loading, error, branches From d5ab116401da99e2fd2c42da55d9134f1f5bc9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 22 Oct 2018 10:46:45 +0200 Subject: [PATCH 76/96] Remove unnecessary route --- scm-ui/src/repos/sources/containers/Sources.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 9646c4b458..4183e2197f 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,7 +1,7 @@ // @flow import React from "react"; import { connect } from "react-redux"; -import { Route, Switch, withRouter } from "react-router-dom"; +import { withRouter } from "react-router-dom"; import type { Repository, Branch } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; @@ -62,20 +62,7 @@ class Sources extends React.Component { return ( <> {this.renderBranchSelector()} - - ( - - )} - /> - ( - - )} - /> - + ); } From 42357f316e65e5fd532a448b2e3239c91ab66259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 22 Oct 2018 11:26:03 +0200 Subject: [PATCH 77/96] Check whether path is set --- scm-ui/src/repos/sources/containers/Sources.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 4183e2197f..a39ffff0b7 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -41,7 +41,11 @@ class Sources extends React.Component { const { baseUrl, history, path } = this.props; let url; if (branch) { - url = `${baseUrl}/${branch.name}/${path}`; + if (path) { + url = `${baseUrl}/${branch.name}/${path}`; + } else { + url = `${baseUrl}/${branch.name}/`; + } } else { url = `${baseUrl}/`; } From 6233e600f2753c67aec09bf89e92dba9195a3b2a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 22 Oct 2018 13:00:44 +0200 Subject: [PATCH 78/96] fix missing translate (t) function --- scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js | 2 +- scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js | 2 +- scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js index f17dc5e2e9..c6aed483e7 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js +++ b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.js @@ -12,7 +12,7 @@ type Props = { class ProtocolInformation extends React.Component { render() { - const { repository } = this.props; + const { repository, t } = this.props; const href = repositories.getProtocolLinkByType(repository, "http"); if (!href) { return null; diff --git a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js index 6a55578926..a8ae91cdfb 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js +++ b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.js @@ -12,7 +12,7 @@ type Props = { class ProtocolInformation extends React.Component { render() { - const { repository } = this.props; + const { repository, t } = this.props; const href = repositories.getProtocolLinkByType(repository, "http"); if (!href) { return null; diff --git a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js index bcfe223b83..68fdc68f74 100644 --- a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js +++ b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.js @@ -12,7 +12,7 @@ type Props = { class ProtocolInformation extends React.Component { render() { - const { repository } = this.props; + const { repository, t } = this.props; const href = repositories.getProtocolLinkByType(repository, "http"); if (!href) { return null; From 66776c54dd7e16c3b36ca32d3514bd160acbe4a9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 22 Oct 2018 14:15:21 +0200 Subject: [PATCH 79/96] fix missing translations in livereload mode --- scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java index 6405c16ddc..222800c9e3 100644 --- a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java @@ -23,6 +23,7 @@ import java.net.URL; * @since 2.0.0 */ @Singleton +@Priority(WebResourceServlet.PRIORITY) @WebElement(value = WebResourceServlet.PATTERN, regex = true) public class WebResourceServlet extends HttpServlet { @@ -33,7 +34,10 @@ public class WebResourceServlet extends HttpServlet { * TODO remove old protocol servlets and hook. Move /hook/hg to api? */ @VisibleForTesting - static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/|locales/).*"; + static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/).*"; + + // Be sure that this servlet is the last one in the servlet chain. + static final int PRIORITY = Integer.MAX_VALUE; private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class); From a7d14636dcd7115aeec31a03f4fe32c6edf9767c Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Mon, 22 Oct 2018 17:37:10 +0200 Subject: [PATCH 80/96] use JsonNode in the I18nServlet --- .../java/sonia/scm/util/JacksonUtils.java | 49 ++++++++++ .../java/sonia/scm/web/i18n/I18nServlet.java | 62 ++++++------- .../sonia/scm/web/i18n/I18nServletTest.java | 93 +++++++++---------- 3 files changed, 124 insertions(+), 80 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java diff --git a/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java b/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java new file mode 100644 index 0000000000..3847b1fb55 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java @@ -0,0 +1,49 @@ +package sonia.scm.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.Iterator; + +public class JacksonUtils { + + private JacksonUtils() { + } + + public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) { + Iterator fieldNames = updateNode.fieldNames(); + + while (fieldNames.hasNext()) { + + String fieldName = fieldNames.next(); + JsonNode jsonNode = mainNode.get(fieldName); + + if (jsonNode != null) { + if (jsonNode.isObject()) { + merge(jsonNode, updateNode.get(fieldName)); + } else if (jsonNode.isArray()) { + for (int i = 0; i < jsonNode.size(); i++) { + merge(jsonNode.get(i), updateNode.get(fieldName).get(i)); + } + } + } else { + if (mainNode instanceof ObjectNode) { + // Overwrite field + JsonNode value = updateNode.get(fieldName); + if (value.isNull()) { + continue; + } + if (value.isIntegralNumber() && value.toString().equals("0")) { + continue; + } + if (value.isFloatingPointNumber() && value.toString().equals("0.0")) { + continue; + } + ((ObjectNode) mainNode).put(fieldName, value); + } + } + } + + return mainNode; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index d0c78bf1f5..16e8c974e7 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -1,6 +1,7 @@ package sonia.scm.web.i18n; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.legman.Subscribe; import com.google.inject.Singleton; @@ -13,7 +14,7 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.filter.WebElement; import sonia.scm.plugin.PluginLoader; -import sonia.scm.plugin.UberClassLoader; +import sonia.scm.util.JacksonUtils; import javax.inject.Inject; import javax.servlet.http.HttpServlet; @@ -22,11 +23,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; @@ -45,29 +42,29 @@ public class I18nServlet extends HttpServlet { public static final String PATTERN = PATH + "/[a-z\\-A-Z]*/" + PLUGINS_JSON; public static final String CACHE_NAME = "sonia.cache.plugins.translations"; - private final UberClassLoader uberClassLoader; - private final Cache cache; + private final ClassLoader classLoader; + private final Cache cache; private static ObjectMapper objectMapper = new ObjectMapper(); @Inject public I18nServlet(PluginLoader pluginLoader, CacheManager cacheManager) { - this.uberClassLoader = (UberClassLoader) pluginLoader.getUberClassLoader(); + this.classLoader = pluginLoader.getUberClassLoader(); this.cache = cacheManager.getCache(CACHE_NAME); } @Subscribe(async = false) public void handleRestartEvent(RestartEvent event) { - log.info("Clear cache on restart event with reason {}", event.getReason()); + log.debug("Clear cache on restart event with reason {}", event.getReason()); cache.clear(); } - private Map getCollectedJson(String path, - Function> jsonFileProvider, - BiConsumer createdJsonFileConsumer) { + private JsonNode getCollectedJson(String path, + Function> jsonFileProvider, + BiConsumer createdJsonFileConsumer) { return Optional.ofNullable(jsonFileProvider.apply(path) .orElseGet(() -> { - Optional createdFile = collectJsonFile(path); + Optional createdFile = collectJsonFile(path); createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map)); return createdFile.orElse(null); } @@ -76,21 +73,20 @@ public class I18nServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) { - try { + try (PrintWriter out = response.getWriter()) { response.setContentType("application/json"); - PrintWriter out = response.getWriter(); String path = req.getServletPath(); - Function> jsonFileProvider = usedPath -> Optional.empty(); - BiConsumer createdJsonFileConsumer = (usedPath, foundJsonMap) -> log.info("A json File is created from the path {}", usedPath); + Function> jsonFileProvider = usedPath -> Optional.empty(); + BiConsumer createdJsonFileConsumer = (usedPath, jsonNode) -> log.debug("A json File is created from the path {}", usedPath); if (isProductionStage()) { - log.info("In Production Stage get the plugin translations from the cache"); + log.debug("In Production Stage get the plugin translations from the cache"); jsonFileProvider = usedPath -> Optional.ofNullable( cache.get(usedPath)); createdJsonFileConsumer = createdJsonFileConsumer - .andThen((usedPath, map) -> log.info("Put the created json File in the cache with the key {}", usedPath)) + .andThen((usedPath, jsonNode) -> log.debug("Put the created json File in the cache with the key {}", usedPath)) .andThen(cache::put); } - out.write(objectMapper.writeValueAsString(getCollectedJson(path, jsonFileProvider, createdJsonFileConsumer))); + objectMapper.writeValue(out, getCollectedJson(path, jsonFileProvider, createdJsonFileConsumer)); } catch (IOException e) { log.error("Error on getting the translation of the plugins", e); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); @@ -105,27 +101,29 @@ public class I18nServlet extends HttpServlet { } /** - * Return a collected Json File as map from the given path from all plugins in the class path + * Return a collected Json File as JsonNode from the given path from all plugins in the class path * * @param path the searched resource path - * @return a collected Json File as map from the given path from all plugins in the class path + * @return a collected Json File as JsonNode from the given path from all plugins in the class path */ - protected Optional collectJsonFile(String path) { - log.info("Collect plugin translations from path {} for every plugin", path); - Map result = null; + protected Optional collectJsonFile(String path) { + log.debug("Collect plugin translations from path {} for every plugin", path); + JsonNode mergedJsonNode = null; try { - Enumeration resources = uberClassLoader.getResources(path.replaceFirst("/", "")); - if (resources.hasMoreElements()) { - result = new HashMap(); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - result.putAll(objectMapper.readValue(Files.readAllBytes(Paths.get(url.getPath())), Map.class)); + Enumeration resources = classLoader.getResources(path.replaceFirst("/", "")); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + JsonNode jsonNode = objectMapper.readTree(url); + if (mergedJsonNode != null) { + JacksonUtils.merge(mergedJsonNode, jsonNode); + } else { + mergedJsonNode = jsonNode; } } } catch (IOException e) { log.error("Error on loading sources from {}", path, e); return Optional.empty(); } - return Optional.ofNullable(result); + return Optional.ofNullable(mergedJsonNode); } } diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index b8deb4b069..243a9bd6ed 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -1,17 +1,18 @@ package sonia.scm.web.i18n; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import com.google.common.base.Charsets; import com.google.common.io.Files; +import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockSettings; import org.mockito.internal.creation.MockSettingsImpl; @@ -22,23 +23,23 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.event.ScmEventBus; import sonia.scm.plugin.PluginLoader; -import sonia.scm.plugin.UberClassLoader; +import sonia.scm.util.JacksonUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; +import java.nio.charset.Charset; import java.util.Collections; import java.util.Enumeration; -import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -60,7 +61,7 @@ public class I18nServletTest { " \"replace\" : \"Push\"\n" + " }\n" + " }\n" + - "}\n"; + "}"; private static final String HG_PLUGIN_JSON = "{\n" + " \"scm-hg-plugin\": {\n" + " \"information\": {\n" + @@ -69,14 +70,14 @@ public class I18nServletTest { " \"replace\" : \"Push\"\n" + " }\n" + " }\n" + - "}\n"; + "}"; private static String SVN_PLUGIN_JSON = "{\n" + " \"scm-svn-plugin\": {\n" + " \"information\": {\n" + " \"checkout\" : \"Checkout\"\n" + " }\n" + " }\n" + - "}\n"; + "}"; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -88,7 +89,7 @@ public class I18nServletTest { CacheManager cacheManager; @Mock - UberClassLoader uberClassLoader; + ClassLoader classLoader; I18nServlet servlet; @@ -104,7 +105,7 @@ public class I18nServletTest { createFileFromString(GIT_PLUGIN_JSON).toURL(), createFileFromString(HG_PLUGIN_JSON).toURL() )); - when(pluginLoader.getUberClassLoader()).thenReturn(uberClassLoader); + when(pluginLoader.getUberClassLoader()).thenReturn(classLoader); when(cacheManager.getCache(I18nServlet.CACHE_NAME)).thenReturn(cache); MockSettings settings = new MockSettingsImpl<>(); settings.useConstructor(pluginLoader, cacheManager); @@ -130,7 +131,7 @@ public class I18nServletTest { PrintWriter writer = mock(PrintWriter.class); when(response.getWriter()).thenReturn(writer); when(request.getServletPath()).thenReturn(path); - when(uberClassLoader.getResources("locales/de/plugins.json")).thenThrow(IOException.class); + when(classLoader.getResources("locales/de/plugins.json")).thenThrow(IOException.class); servlet.doGet(request, response); @@ -140,14 +141,9 @@ public class I18nServletTest { @Test @SuppressWarnings("unchecked") public void shouldFailWith500OnIOException() throws IOException { - String path = "/locales/de/plugins.json"; HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - PrintWriter writer = mock(PrintWriter.class); - when(response.getWriter()).thenReturn(writer); - when(request.getServletPath()).thenReturn(path); - when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); - doThrow(IOException.class).when(writer).write(any(String.class)); + doThrow(IOException.class).when(response).getWriter(); servlet.doGet(request, response); @@ -161,16 +157,16 @@ public class I18nServletTest { when(servlet.isProductionStage()).thenReturn(false); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - PrintWriter writer = mock(PrintWriter.class); + File file = temporaryFolder.newFile(); + PrintWriter writer = new PrintWriter(new FileOutputStream(file)); when(response.getWriter()).thenReturn(writer); when(request.getServletPath()).thenReturn(path); - when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - doCallRealMethod().when(writer).write(captor.capture()); + when(classLoader.getResources("locales/de/plugins.json")).thenReturn(resources); servlet.doGet(request, response); - assertJsonMap(jsonStringToMap(captor.getValue())); + String json = Files.readLines(file, Charset.defaultCharset()).get(0); + assertJson(json); verify(cache, never()).get(any()); } @@ -181,16 +177,16 @@ public class I18nServletTest { when(servlet.isProductionStage()).thenReturn(true); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - PrintWriter writer = mock(PrintWriter.class); + File file = temporaryFolder.newFile(); + PrintWriter writer = new PrintWriter(new FileOutputStream(file)); when(response.getWriter()).thenReturn(writer); when(request.getServletPath()).thenReturn(path); - when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - doCallRealMethod().when(writer).write(captor.capture()); + when(classLoader.getResources("locales/de/plugins.json")).thenReturn(resources); servlet.doGet(request, response); - assertJsonMap(jsonStringToMap(captor.getValue())); + String json = Files.readLines(file, Charset.defaultCharset()).get(0); + assertJson(json); verify(cache).get(path); verify(cache).put(eq(path), any()); } @@ -202,41 +198,47 @@ public class I18nServletTest { when(servlet.isProductionStage()).thenReturn(true); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - PrintWriter writer = mock(PrintWriter.class); + File file = temporaryFolder.newFile(); + PrintWriter writer = new PrintWriter(new FileOutputStream(file)); when(response.getWriter()).thenReturn(writer); when(request.getServletPath()).thenReturn(path); - when(uberClassLoader.getResources("locales/de/plugins.json")).thenReturn(resources); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - doCallRealMethod().when(writer).write(captor.capture()); - Map cachedMap = jsonStringToMap(GIT_PLUGIN_JSON); - cachedMap.putAll(jsonStringToMap(HG_PLUGIN_JSON)); - cachedMap.putAll(jsonStringToMap(SVN_PLUGIN_JSON)); - when(cache.get(path)).thenReturn(cachedMap); + when(classLoader.getResources("locales/de/plugins.json")).thenReturn(resources); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode node = objectMapper.readTree(GIT_PLUGIN_JSON); + node = JacksonUtils.merge(node, objectMapper.readTree(HG_PLUGIN_JSON)); + node = JacksonUtils.merge(node, objectMapper.readTree(SVN_PLUGIN_JSON)); + when(cache.get(path)).thenReturn(node); + servlet.doGet(request, response); + + String json = Files.readLines(file, Charset.defaultCharset()).get(0); verify(servlet, never()).collectJsonFile(path); verify(cache, never()).put(eq(path), any()); verify(cache).get(path); - assertJsonMap(jsonStringToMap(captor.getValue())); + assertJson(json); } @Test @SuppressWarnings("unchecked") public void shouldCollectJsonFile() throws IOException { String path = "locales/de/plugins.json"; + when(classLoader.getResources(path)).thenReturn(resources); - when(uberClassLoader.getResources(path)).thenReturn(resources); - Optional mapOptional = servlet.collectJsonFile("/" + path); + Optional jsonNodeOptional = servlet.collectJsonFile("/" + path); - assertJsonMap(mapOptional.orElse(null)); + assertJson(jsonNodeOptional.orElse(null)); } - @SuppressWarnings("unchecked") - public void assertJsonMap(Map actual) throws IOException { + public void assertJson(JsonNode actual) throws IOException { + assertJson(actual.toString()); + } + + public void assertJson(String actual) throws IOException { assertThat(actual) .isNotEmpty() - .containsAllEntriesOf(jsonStringToMap(GIT_PLUGIN_JSON)) - .containsAllEntriesOf(jsonStringToMap(HG_PLUGIN_JSON)) - .containsAllEntriesOf(jsonStringToMap(SVN_PLUGIN_JSON)); + .contains(StringUtils.deleteWhitespace(GIT_PLUGIN_JSON.substring(1, GIT_PLUGIN_JSON.length() - 1))) + .contains(StringUtils.deleteWhitespace(HG_PLUGIN_JSON.substring(1, HG_PLUGIN_JSON.length() - 1))) + .contains(StringUtils.deleteWhitespace(SVN_PLUGIN_JSON.substring(1, SVN_PLUGIN_JSON.length() - 1))); } public File createFileFromString(String json) throws IOException { @@ -245,9 +247,4 @@ public class I18nServletTest { return file; } - private Map jsonStringToMap(String fileAsString) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(fileAsString, Map.class); - } - } From 0834420e4b1074272100575bc198c389601300f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 10:41:10 +0200 Subject: [PATCH 81/96] Fix rendering trigger --- scm-ui/src/repos/containers/RepositoryRoot.js | 16 ++++------------ scm-ui/src/repos/sources/components/FileTree.js | 14 ++++++++++---- scm-ui/src/repos/sources/containers/Sources.js | 14 +++++++++++--- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 0ef03c4d43..f492f114bc 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -138,22 +138,14 @@ class RepositoryRoot extends React.Component { ( - + render={() => ( + )} /> ( - + render={() => ( + )} /> { 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, @@ -138,18 +145,17 @@ class FileTree extends React.Component { } const mapStateToProps = (state: any, ownProps: Props) => { - const { repository, match } = ownProps; - const { revision, path } = match.params; + const { repository, revision, path } = ownProps; const loading = isFetchSourcesPending(state, repository, revision, path); const error = getFetchSourcesFailure(state, repository, revision, path); const tree = getSources(state, repository, revision, path); return { - loading, - error, revision, path, + loading, + error, tree }; }; diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index a39ffff0b7..9d9374e785 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -20,6 +20,7 @@ type Props = { error: Error, baseUrl: string, branches: Branch[], + revision: string, path: string, // dispatch props @@ -39,6 +40,7 @@ class Sources extends React.Component { branchSelected = (branch?: Branch) => { const { baseUrl, history, path } = this.props; + let url; if (branch) { if (path) { @@ -53,7 +55,7 @@ class Sources extends React.Component { }; render() { - const { repository, baseUrl, loading, error } = this.props; + const { repository, baseUrl, loading, error, revision, path } = this.props; if (error) { return ; @@ -66,7 +68,12 @@ class Sources extends React.Component { return ( <> {this.renderBranchSelector()} - + ); } @@ -89,7 +96,7 @@ class Sources extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository, match } = ownProps; - const { path } = match.params; + const { revision, path } = match.params; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); @@ -97,6 +104,7 @@ const mapStateToProps = (state, ownProps) => { return { repository, + revision, path, loading, error, From fa7626619c06e753f197b607b8f1c9c44c9d1f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 11:24:23 +0200 Subject: [PATCH 82/96] Select current revision in selector if it is a branch --- scm-ui/src/repos/containers/BranchSelector.js | 7 +++++++ scm-ui/src/repos/sources/containers/Sources.js | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/containers/BranchSelector.js b/scm-ui/src/repos/containers/BranchSelector.js index b5c628df12..2183e13b69 100644 --- a/scm-ui/src/repos/containers/BranchSelector.js +++ b/scm-ui/src/repos/containers/BranchSelector.js @@ -17,6 +17,7 @@ const styles = { type Props = { branches: Branch[], // TODO: Use generics? selected: (branch?: Branch) => void, + selectedBranch: string, // context props classes: Object, @@ -31,6 +32,12 @@ class BranchSelector extends React.Component { this.state = {}; } + componentDidMount() { + this.props.branches + .filter(branch => branch.name === this.props.selectedBranch) + .forEach(branch => this.setState({ selectedBranch: branch })); + } + render() { const { branches, classes, t } = this.props; diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 9d9374e785..7e7dd504aa 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -79,11 +79,12 @@ class Sources extends React.Component { } renderBranchSelector = () => { - const { repository, branches } = this.props; + const { repository, branches, revision } = this.props; if (repository._links.branches) { return ( { this.branchSelected(b); }} From f44f352a2ab561982f42fbf1069c585db7ed8efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 13:02:45 +0200 Subject: [PATCH 83/96] Set selected branch --- scm-ui/src/repos/containers/BranchRoot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index c60dc7281a..1f3f0c1e3b 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -92,11 +92,12 @@ class BranchRoot extends React.Component { } renderBranchSelector = () => { - const { repository, branches } = this.props; + const { repository, branches, selected } = this.props; if (repository._links.branches) { return ( { this.branchSelected(b); }} From df8cc25bcca4ab117a5e09cbf1f26f67c07e0ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 13:03:15 +0200 Subject: [PATCH 84/96] Encode branch in urls --- scm-ui/src/repos/sources/containers/Sources.js | 7 ++++--- scm-ui/src/repos/sources/modules/sources.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 7e7dd504aa..cf072e958e 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -44,9 +44,9 @@ class Sources extends React.Component { let url; if (branch) { if (path) { - url = `${baseUrl}/${branch.name}/${path}`; + url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`; } else { - url = `${baseUrl}/${branch.name}/`; + url = `${baseUrl}/${encodeURIComponent(branch.name)}/`; } } else { url = `${baseUrl}/`; @@ -98,6 +98,7 @@ class Sources extends React.Component { 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); @@ -105,7 +106,7 @@ const mapStateToProps = (state, ownProps) => { return { repository, - revision, + revision: decodedRevision, path, loading, error, diff --git a/scm-ui/src/repos/sources/modules/sources.js b/scm-ui/src/repos/sources/modules/sources.js index fb606228a5..719770d75c 100644 --- a/scm-ui/src/repos/sources/modules/sources.js +++ b/scm-ui/src/repos/sources/modules/sources.js @@ -39,7 +39,7 @@ function createUrl(repository: Repository, revision: string, path: string) { // TODO handle trailing slash const pathDefined = path ? path : ""; - return `${base}${revision}/${pathDefined}`; + return `${base}${encodeURIComponent(revision)}/${pathDefined}`; } export function fetchSourcesPending( From 26e11c39ace3091e23f3bfa799c7bf40e35c7f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 13:17:49 +0200 Subject: [PATCH 85/96] Rename class --- .../src/repos/containers/{BranchRoot.js => ChangesetsRoot.js} | 0 scm-ui/src/repos/containers/RepositoryRoot.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename scm-ui/src/repos/containers/{BranchRoot.js => ChangesetsRoot.js} (100%) diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js similarity index 100% rename from scm-ui/src/repos/containers/BranchRoot.js rename to scm-ui/src/repos/containers/ChangesetsRoot.js diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index f492f114bc..902f2e11f1 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -29,7 +29,7 @@ import Permissions from "../permissions/containers/Permissions"; import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; -import BranchRoot from "./BranchRoot"; +import BranchRoot from "./ChangesetsRoot"; import ChangesetView from "./ChangesetView"; import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; From 46b0bbf63628c2666b5154761b5f509150142857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 23 Oct 2018 13:40:27 +0200 Subject: [PATCH 86/96] Fetch missing file list in server result --- scm-ui/src/repos/sources/components/FileTree.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 1050eb14af..5d7e31274c 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -102,6 +102,7 @@ class FileTree extends React.Component { } const files = []; + if (path) { files.push({ name: "..", @@ -110,7 +111,9 @@ class FileTree extends React.Component { }); } - files.push(...tree._embedded.children.sort(compareFiles)); + if (tree._embedded) { + files.push(...tree._embedded.children.sort(compareFiles)); + } let baseUrlWithRevision = baseUrl; if (revision) { From 05a872f9b66586f5138eb9bb48418c3e4375f4f9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:11:42 +0200 Subject: [PATCH 87/96] place source link below commits and show commit link only if changesets are supported by the repository --- scm-ui/src/repos/containers/RepositoryRoot.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 902f2e11f1..9475612765 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -174,16 +174,13 @@ class RepositoryRoot extends React.Component {
    - - - { label={t("repository-root.sources")} activeOnlyWhenExact={false} /> + +
    From 9b210fd2125b27cf86fd1331c279eba7d436cada Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:12:07 +0200 Subject: [PATCH 88/96] pass all properties of RepositoryNavLink to NavLink --- scm-ui/src/repos/components/RepositoryNavLink.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scm-ui/src/repos/components/RepositoryNavLink.js b/scm-ui/src/repos/components/RepositoryNavLink.js index 4e315bcf9f..b4cf7774af 100644 --- a/scm-ui/src/repos/components/RepositoryNavLink.js +++ b/scm-ui/src/repos/components/RepositoryNavLink.js @@ -8,6 +8,7 @@ type Props = { to: string, label: string, linkName: string, + activeWhenMatch?: (route: any) => boolean, activeOnlyWhenExact: boolean }; @@ -16,19 +17,13 @@ type Props = { */ class RepositoryNavLink extends React.Component { render() { - const { linkName, to, label, repository, activeOnlyWhenExact } = this.props; + const { repository, linkName } = this.props; if (!repository._links[linkName]) { return null; } - return ( - - ); + return ; } } From 43114a11af200932ace7a78de18a733090f99498 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:14:42 +0200 Subject: [PATCH 89/96] hide length and last modified columns on mobile --- scm-ui/src/repos/sources/components/FileTree.js | 8 ++++++-- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 5d7e31274c..ed28652adf 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -128,8 +128,12 @@ class FileTree extends React.Component {
    - - + + diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 5224ba3ded..033d3b9b8a 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -68,8 +68,8 @@ class FileTreeLeaf extends React.Component { - - + From e675c4358a65e24a8ccff8e53b7553a6461ca4eb Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:16:52 +0200 Subject: [PATCH 90/96] fix lost encoding on directory change --- scm-ui/src/repos/sources/components/FileTree.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index ed28652adf..02aa22f942 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -117,9 +117,9 @@ class FileTree extends React.Component { let baseUrlWithRevision = baseUrl; if (revision) { - baseUrlWithRevision += "/" + revision; + baseUrlWithRevision += "/" + encodeURIComponent(revision); } else { - baseUrlWithRevision += "/" + tree.revision; + baseUrlWithRevision += "/" + encodeURIComponent(tree.revision); } return ( From 36a55bffaeb03d7a67410979ef8b567e326d48cd Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 24 Oct 2018 10:18:09 +0200 Subject: [PATCH 91/96] Move the json merge in the I18nServlet class --- .../java/sonia/scm/util/JacksonUtils.java | 49 -------------- .../java/sonia/scm/web/i18n/I18nServlet.java | 67 +++++++++++++++++-- .../sonia/scm/web/i18n/I18nServletTest.java | 5 +- 3 files changed, 65 insertions(+), 56 deletions(-) delete mode 100644 scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java diff --git a/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java b/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java deleted file mode 100644 index 3847b1fb55..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/util/JacksonUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -package sonia.scm.util; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import java.util.Iterator; - -public class JacksonUtils { - - private JacksonUtils() { - } - - public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) { - Iterator fieldNames = updateNode.fieldNames(); - - while (fieldNames.hasNext()) { - - String fieldName = fieldNames.next(); - JsonNode jsonNode = mainNode.get(fieldName); - - if (jsonNode != null) { - if (jsonNode.isObject()) { - merge(jsonNode, updateNode.get(fieldName)); - } else if (jsonNode.isArray()) { - for (int i = 0; i < jsonNode.size(); i++) { - merge(jsonNode.get(i), updateNode.get(fieldName).get(i)); - } - } - } else { - if (mainNode instanceof ObjectNode) { - // Overwrite field - JsonNode value = updateNode.get(fieldName); - if (value.isNull()) { - continue; - } - if (value.isIntegralNumber() && value.toString().equals("0")) { - continue; - } - if (value.isFloatingPointNumber() && value.toString().equals("0.0")) { - continue; - } - ((ObjectNode) mainNode).put(fieldName, value); - } - } - } - - return mainNode; - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index 16e8c974e7..9773b91cf7 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -3,7 +3,9 @@ package sonia.scm.web.i18n; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.legman.Subscribe; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Singleton; import lombok.extern.slf4j.Slf4j; import sonia.scm.NotFoundException; @@ -14,7 +16,6 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.filter.WebElement; import sonia.scm.plugin.PluginLoader; -import sonia.scm.util.JacksonUtils; import javax.inject.Inject; import javax.servlet.http.HttpServlet; @@ -24,6 +25,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.URL; import java.util.Enumeration; +import java.util.Iterator; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; @@ -37,9 +39,8 @@ import java.util.function.Function; @Slf4j public class I18nServlet extends HttpServlet { - public static final String PATH = "/locales"; public static final String PLUGINS_JSON = "plugins.json"; - public static final String PATTERN = PATH + "/[a-z\\-A-Z]*/" + PLUGINS_JSON; + public static final String PATTERN = "/locales/[a-z\\-A-Z]*/" + PLUGINS_JSON; public static final String CACHE_NAME = "sonia.cache.plugins.translations"; private final ClassLoader classLoader; @@ -71,6 +72,7 @@ public class I18nServlet extends HttpServlet { )).orElseThrow(NotFoundException::new); } + @VisibleForTesting @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) { try (PrintWriter out = response.getWriter()) { @@ -96,6 +98,7 @@ public class I18nServlet extends HttpServlet { } } + @VisibleForTesting protected boolean isProductionStage() { return SCMContext.getContext().getStage() == Stage.PRODUCTION; } @@ -106,6 +109,7 @@ public class I18nServlet extends HttpServlet { * @param path the searched resource path * @return a collected Json File as JsonNode from the given path from all plugins in the class path */ + @VisibleForTesting protected Optional collectJsonFile(String path) { log.debug("Collect plugin translations from path {} for every plugin", path); JsonNode mergedJsonNode = null; @@ -115,7 +119,7 @@ public class I18nServlet extends HttpServlet { URL url = resources.nextElement(); JsonNode jsonNode = objectMapper.readTree(url); if (mergedJsonNode != null) { - JacksonUtils.merge(mergedJsonNode, jsonNode); + merge(mergedJsonNode, jsonNode); } else { mergedJsonNode = jsonNode; } @@ -126,4 +130,59 @@ public class I18nServlet extends HttpServlet { } return Optional.ofNullable(mergedJsonNode); } + + + /** + * Merge the updateNode into the mainNode and return it. + * + * This is not a deep merge. + * + * @param mainNode the main node + * @param updateNode the update node + * @return the merged mainNode + */ + @VisibleForTesting + protected JsonNode merge(JsonNode mainNode, JsonNode updateNode) { + Iterator fieldNames = updateNode.fieldNames(); + + while (fieldNames.hasNext()) { + + String fieldName = fieldNames.next(); + JsonNode jsonNode = mainNode.get(fieldName); + + if (jsonNode != null) { + mergeNode(updateNode, fieldName, jsonNode); + } else { + mergeField(mainNode, updateNode, fieldName); + } + } + return mainNode; + } + + private void mergeField(JsonNode mainNode, JsonNode updateNode, String fieldName) { + if (mainNode instanceof ObjectNode) { + JsonNode value = updateNode.get(fieldName); + if (value.isNull()) { + return; + } + if (value.isIntegralNumber() && value.toString().equals("0")) { + return; + } + if (value.isFloatingPointNumber() && value.toString().equals("0.0")) { + return; + } + ((ObjectNode) mainNode).set(fieldName, value); + } + } + + private void mergeNode(JsonNode updateNode, String fieldName, JsonNode jsonNode) { + if (jsonNode.isObject()) { + merge(jsonNode, updateNode.get(fieldName)); + } else if (jsonNode.isArray()) { + for (int i = 0; i < jsonNode.size(); i++) { + merge(jsonNode.get(i), updateNode.get(fieldName).get(i)); + } + } + } + } diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index 243a9bd6ed..6f0e0d07d9 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -23,7 +23,6 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.event.ScmEventBus; import sonia.scm.plugin.PluginLoader; -import sonia.scm.util.JacksonUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -205,8 +204,8 @@ public class I18nServletTest { when(classLoader.getResources("locales/de/plugins.json")).thenReturn(resources); ObjectMapper objectMapper = new ObjectMapper(); JsonNode node = objectMapper.readTree(GIT_PLUGIN_JSON); - node = JacksonUtils.merge(node, objectMapper.readTree(HG_PLUGIN_JSON)); - node = JacksonUtils.merge(node, objectMapper.readTree(SVN_PLUGIN_JSON)); + node = servlet.merge(node, objectMapper.readTree(HG_PLUGIN_JSON)); + node = servlet.merge(node, objectMapper.readTree(SVN_PLUGIN_JSON)); when(cache.get(path)).thenReturn(node); servlet.doGet(request, response); From 7fff5a62c975390da48c278190f7c60b5331d1f7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:25:50 +0200 Subject: [PATCH 92/96] do not fail if the description is empty --- scm-ui/src/repos/components/changesets/changesets.js | 11 ++++++----- .../repos/components/changesets/changesets.test.js | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/scm-ui/src/repos/components/changesets/changesets.js b/scm-ui/src/repos/components/changesets/changesets.js index 9d4821e5ec..f61a89c74b 100644 --- a/scm-ui/src/repos/components/changesets/changesets.js +++ b/scm-ui/src/repos/components/changesets/changesets.js @@ -4,17 +4,18 @@ export type Description = { message: string }; -export function parseDescription(description: string): Description { - const lineBreak = description.indexOf("\n"); +export function parseDescription(description?: string): Description { + const desc = description ? description : ""; + const lineBreak = desc.indexOf("\n"); let title; let message = ""; if (lineBreak > 0) { - title = description.substring(0, lineBreak); - message = description.substring(lineBreak + 1); + title = desc.substring(0, lineBreak); + message = desc.substring(lineBreak + 1); } else { - title = description; + title = desc; } return { diff --git a/scm-ui/src/repos/components/changesets/changesets.test.js b/scm-ui/src/repos/components/changesets/changesets.test.js index cd3788e9fb..ea92bcead3 100644 --- a/scm-ui/src/repos/components/changesets/changesets.test.js +++ b/scm-ui/src/repos/components/changesets/changesets.test.js @@ -13,4 +13,10 @@ describe("parseDescription tests", () => { const desc = parseDescription("Hello Trillian"); expect(desc.title).toBe("Hello Trillian"); }); + + it("should return an empty description for undefined", () => { + const desc = parseDescription(); + expect(desc.title).toBe(""); + expect(desc.message).toBe(""); + }); }); From 660604b821da28f7d95101e31e52dedf9b51a33f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 10:34:14 +0200 Subject: [PATCH 93/96] show different icon for sub repository --- scm-ui/src/repos/sources/components/FileIcon.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/components/FileIcon.js b/scm-ui/src/repos/sources/components/FileIcon.js index 4615c0cddc..97aa5bc6f6 100644 --- a/scm-ui/src/repos/sources/components/FileIcon.js +++ b/scm-ui/src/repos/sources/components/FileIcon.js @@ -10,7 +10,9 @@ class FileIcon extends React.Component { render() { const { file } = this.props; let icon = "file"; - if (file.directory) { + if (file.subRepository) { + icon = "folder-plus"; + } else if (file.directory) { icon = "folder"; } return ; From cb89c19effc1a905a405d36751f3bf4bbe922b4e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 09:06:28 +0000 Subject: [PATCH 94/96] Close branch feature/ui-sources From 96faa3cad76d0fbbc61cae5e0f35e1f4064899c1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 11:15:55 +0200 Subject: [PATCH 95/96] simplify declaration of test data --- .../sonia/scm/web/i18n/I18nServletTest.java | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index 6f0e0d07d9..a912f738e2 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -39,11 +39,7 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.Silent.class) @SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") @@ -52,31 +48,41 @@ public class I18nServletTest { @Rule public ShiroRule shiro = new ShiroRule(); - private static final String GIT_PLUGIN_JSON = "{\n" + - " \"scm-git-plugin\": {\n" + - " \"information\": {\n" + - " \"clone\" : \"Clone\",\n" + - " \"create\" : \"Create\",\n" + - " \"replace\" : \"Push\"\n" + - " }\n" + - " }\n" + - "}"; - private static final String HG_PLUGIN_JSON = "{\n" + - " \"scm-hg-plugin\": {\n" + - " \"information\": {\n" + - " \"clone\" : \"Clone\",\n" + - " \"create\" : \"Create\",\n" + - " \"replace\" : \"Push\"\n" + - " }\n" + - " }\n" + - "}"; - private static String SVN_PLUGIN_JSON = "{\n" + - " \"scm-svn-plugin\": {\n" + - " \"information\": {\n" + - " \"checkout\" : \"Checkout\"\n" + - " }\n" + - " }\n" + - "}"; + private static final String GIT_PLUGIN_JSON = json( + "{", + "'scm-git-plugin': {", + "'information': {", + "'clone' : 'Clone',", + "'create' : 'Create',", + "'replace' : 'Push'", + "}", + "}", + "}" + ); + private static final String HG_PLUGIN_JSON = json( + "{", + "'scm-hg-plugin': {", + "'information': {", + "'clone' : 'Clone',", + "'create' : 'Create',", + "'replace' : 'Push'", + "}", + "}", + "}" + ); + private static String SVN_PLUGIN_JSON = json( + "{", + "'scm-svn-plugin': {", + "'information': {", + "'checkout' : 'Checkout'", + "}", + "}", + "}" + ); + + private static String json(String... parts) { + return String.join("\n", parts ).replaceAll("'", "\""); + } @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); From 9672d2c2b8f5843a2266a2a550413ae4d83aded2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Oct 2018 09:22:47 +0000 Subject: [PATCH 96/96] Close branch feature/i18n_for_plugins_v2
    {t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.lastModified")} + {t("sources.file-tree.length")} + + {t("sources.file-tree.lastModified")} + {t("sources.file-tree.description")}
    {this.createFileIcon(file)} {this.createFileName(file)}{fileSize} + {fileSize} {file.description}