diff options
author | Stan Hu <stanhu@gmail.com> | 2017-08-10 01:57:52 +0300 |
---|---|---|
committer | Stan Hu <stanhu@gmail.com> | 2017-08-10 01:57:52 +0300 |
commit | 09baadca349670822645b56a17e9bf8716e997b6 (patch) | |
tree | 397bb26f3a0e851b39c57351cbe6963adc47dea8 | |
parent | ded77e21b38dbb65aec2aeae42de02e6571fe01a (diff) | |
parent | 2925850ceec3ef89eb1f60b0a648dbc5f72d8683 (diff) |
Merge branch 'master' into sh-headless-chrome-support
1628 files changed, 23418 insertions, 17864 deletions
diff --git a/.eslintrc b/.eslintrc index c72a5e0335b..3e07edbccfe 100644 --- a/.eslintrc +++ b/.eslintrc @@ -30,6 +30,7 @@ "filenames/match-regex": [2, "^[a-z0-9_]+$"], "import/no-commonjs": "error", "no-multiple-empty-lines": ["error", { "max": 1 }], - "promise/catch-or-return": "error" + "promise/catch-or-return": "error", + "no-underscore-dangle": ["error", { "allow": ["__"]}] } } diff --git a/.flayignore b/.flayignore index e2d0a2e50c5..b63ce4c4df0 100644 --- a/.flayignore +++ b/.flayignore @@ -3,4 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/diff/position_tracer.rb app/policies/project_policy.rb app/models/concerns/relative_positioning.rb +app/workers/stuck_merge_jobs_worker.rb lib/gitlab/redis/*.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d93c1fad351..6c0232ace1b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,16 +66,18 @@ stages: - mysql:latest - redis:alpine -.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql +.only-if-want-mysql: &only-if-want-mysql only: - /mysql/ - /-stable/ - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee - master@gitlab/gitlabhq + - master@gitlab/gitlab-ee - tags@gitlab-org/gitlab-ce + - tags@gitlab-org/gitlab-ee - tags@gitlab/gitlabhq - - //@gitlab-org/gitlab-ee - - //@gitlab/gitlab-ee + - tags@gitlab/gitlab-ee # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. @@ -96,6 +98,7 @@ stages: - export KNAPSACK_GENERATE_REPORT=true - export CACHE_CLASSES=true - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} + - scripts/gitaly-test-spawn - knapsack rspec "--color --format documentation" artifacts: expire_in: 31d @@ -113,7 +116,7 @@ stages: .rspec-knapsack-mysql: &rspec-knapsack-mysql <<: *rspec-knapsack <<: *use-mysql - <<: *only-master-and-ee-or-mysql + <<: *only-if-want-mysql <<: *except-docs .spinach-knapsack: &spinach-knapsack @@ -145,7 +148,7 @@ stages: .spinach-knapsack-mysql: &spinach-knapsack-mysql <<: *spinach-knapsack <<: *use-mysql - <<: *only-master-and-ee-or-mysql + <<: *only-if-want-mysql <<: *except-docs .only-canonical-masters: &only-canonical-masters @@ -221,6 +224,7 @@ setup-test-env: - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' + - scripts/gitaly-test-build # Do not use 'bundle exec' here artifacts: expire_in: 7d paths: @@ -485,6 +489,7 @@ karma: BABEL_ENV: "coverage" CHROME_LOG_FILE: "chrome_debug.log" script: + - scripts/gitaly-test-spawn - bundle exec rake gettext:po_to_json - bundle exec rake karma coverage: '/^Statements *: (\d+\.\d+%)/' @@ -508,8 +513,11 @@ codeclimate: services: - docker:dind script: + - cp .rubocop.yml .rubocop.yml.bak + - grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json + - mv .rubocop.yml.bak .rubocop.yml artifacts: paths: [codeclimate.json] diff --git a/.haml-lint.yml b/.haml-lint.yml index 528f99d08d2..32c7de0fb78 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -35,9 +35,21 @@ linters: HtmlAttributes: enabled: true + IdNames: + enabled: false + ImplicitDiv: enabled: true + InlineJavaScript: + enabled: true + + InlineStyles: + enabled: false + + InstanceVariables: + enabled: false + LeadingCommentSpace: enabled: false @@ -54,6 +66,9 @@ linters: ObjectReferenceAttributes: enabled: true + RepeatedId: + enabled: false + RuboCop: enabled: false # These cops are incredibly noisy when it comes to HAML templates, so we @@ -101,3 +116,6 @@ linters: UnnecessaryStringOutput: enabled: true + + ViewLength: + enabled: false diff --git a/.rubocop.yml b/.rubocop.yml index f661a29d9d1..84e4a3c2e49 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,11 +1,13 @@ require: - rubocop-rspec + - rubocop-gitlab-security - ./rubocop/rubocop inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.3 + TargetRailsVersion: 4.2 # Cop names are not d§splayed in offense messages by default. Change behavior # by overriding DisplayCopNames, or by giving the -D/--display-cop-names # option. @@ -29,34 +31,237 @@ AllCops: Bundler/OrderedGems: Enabled: false -# Style ####################################################################### +# Layout ###################################################################### # Check indentation of private/protected visibility modifiers. -Style/AccessModifierIndentation: - Enabled: true - -# Check the naming of accessor methods for get_/set_. -Style/AccessorMethodName: - Enabled: false - -# Use alias_method instead of alias. -Style/Alias: - EnforcedStyle: prefer_alias_method +Layout/AccessModifierIndentation: Enabled: true # Align the elements of an array literal if they span more than one line. -Style/AlignArray: +Layout/AlignArray: Enabled: true # Align the elements of a hash literal if they span more than one line. -Style/AlignHash: +Layout/AlignHash: Enabled: true # Here we check if the parameters on a multi-line method call or # definition are aligned. -Style/AlignParameters: +Layout/AlignParameters: + Enabled: false + +# Put end statement of multiline block on its own line. +Layout/BlockEndNewline: + Enabled: true + +# Indentation of when in a case/when/[else/]end. +Layout/CaseIndentation: + Enabled: true + +# Indentation of comments. +Layout/CommentIndentation: + Enabled: true + +# Multi-line method chaining should be done with leading dots. +Layout/DotPosition: + Enabled: true + EnforcedStyle: leading + +# Align elses and elsifs correctly. +Layout/ElseAlignment: + Enabled: true + +# Add an empty line after magic comments to separate them from code. +Layout/EmptyLineAfterMagicComment: + Enabled: false + +# Use empty lines between defs. +Layout/EmptyLineBetweenDefs: + Enabled: true + +# Don't use several empty lines in a row. +Layout/EmptyLines: + Enabled: true + +# Keep blank lines around access modifiers. +Layout/EmptyLinesAroundAccessModifier: + Enabled: true + +# Keeps track of empty lines around block bodies. +Layout/EmptyLinesAroundBlockBody: + Enabled: true + +# Keeps track of empty lines around class bodies. +Layout/EmptyLinesAroundClassBody: + Enabled: true + +# Keeps track of empty lines around exception handling keywords. +Layout/EmptyLinesAroundExceptionHandlingKeywords: + Enabled: false + +# Keeps track of empty lines around method bodies. +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +# Keeps track of empty lines around module bodies. +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +# Use Unix-style line endings. +Layout/EndOfLine: + Enabled: true + +# Checks for a line break before the first parameter in a multi-line method +# parameter definition. +Layout/FirstMethodParameterLineBreak: + Enabled: true + +# Keep indentation straight. +Layout/IndentationConsistency: + Enabled: true + +# Use 2 spaces for indentation. +Layout/IndentationWidth: + Enabled: true + +# Checks the indentation of the first line of the right-hand-side of a +# multi-line assignment. +Layout/IndentAssignment: + Enabled: true + +# This cops checks the indentation of the here document bodies. +Layout/IndentHeredoc: + Enabled: false + +# Comments should start with a space. +Layout/LeadingCommentSpace: + Enabled: true + +# Checks that the closing brace in an array literal is either on the same line +# as the last array element, or a new line. +Layout/MultilineArrayBraceLayout: + Enabled: true + EnforcedStyle: symmetrical + +# Ensures newlines after multiline block do statements. +Layout/MultilineBlockLayout: + Enabled: true + +# Checks that the closing brace in a hash literal is either on the same line as +# the last hash element, or a new line. +Layout/MultilineHashBraceLayout: + Enabled: true + EnforcedStyle: symmetrical + +# Checks that the closing brace in a method call is either on the same line as +# the last method argument, or a new line. +Layout/MultilineMethodCallBraceLayout: + Enabled: false + EnforcedStyle: symmetrical + +# Checks indentation of method calls with the dot operator that span more than +# one line. +Layout/MultilineMethodCallIndentation: + Enabled: false + +# Checks that the closing brace in a method definition is symmetrical with +# respect to the opening brace and the method parameters. +Layout/MultilineMethodDefinitionBraceLayout: Enabled: false +# Checks indentation of binary operations that span more than one line. +Layout/MultilineOperationIndentation: + Enabled: true + EnforcedStyle: indented + +# Use spaces after colons. +Layout/SpaceAfterColon: + Enabled: true + +# Use spaces after commas. +Layout/SpaceAfterComma: + Enabled: true + +# Do not put a space between a method name and the opening parenthesis in a +# method definition. +Layout/SpaceAfterMethodName: + Enabled: true + +# Tracks redundant space after the ! operator. +Layout/SpaceAfterNot: + Enabled: true + +# Use spaces after semicolons. +Layout/SpaceAfterSemicolon: + Enabled: true + +# Use space around equals in parameter default +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: true + +# Use a space around keywords if appropriate. +Layout/SpaceAroundKeyword: + Enabled: true + +# Use a single space around operators. +Layout/SpaceAroundOperators: + Enabled: true + +# Checks that block braces have or don't have a space before the opening +# brace depending on configuration. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Layout/SpaceBeforeBlockBraces: + Enabled: true + +# No spaces before commas. +Layout/SpaceBeforeComma: + Enabled: true + +# Checks for missing space between code and a comment on the same line. +Layout/SpaceBeforeComment: + Enabled: true + +# No spaces before semicolons. +Layout/SpaceBeforeSemicolon: + Enabled: true + +# Checks for spaces inside square brackets. +Layout/SpaceInsideBrackets: + Enabled: true + +# Use spaces inside hash literal braces - or don't. +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +# No spaces inside range literals. +Layout/SpaceInsideRangeLiteral: + Enabled: true + +# Checks for padding/surrounding spaces inside string interpolation. +Layout/SpaceInsideStringInterpolation: + EnforcedStyle: no_space + Enabled: true + +# No hard tabs. +Layout/Tab: + Enabled: true + +# Checks trailing blank lines and final newline. +Layout/TrailingBlankLines: + Enabled: true + +# Style ####################################################################### + +# Check the naming of accessor methods for get_/set_. +Style/AccessorMethodName: + Enabled: false + +# Use alias_method instead of alias. +Style/Alias: + EnforcedStyle: prefer_alias_method + Enabled: true + # Whether `and` and `or` are banned only in conditionals (conditionals) # or completely (always). Style/AndOr: @@ -91,10 +296,6 @@ Style/BlockComments: Style/BlockDelimiters: Enabled: true -# Put end statement of multiline block on its own line. -Style/BlockEndNewline: - Enabled: true - # This cop checks for braces around the last parameter in a method call # if the last parameter is a hash. Style/BracesAroundHashParameters: @@ -104,10 +305,6 @@ Style/BracesAroundHashParameters: Style/CaseEquality: Enabled: false -# Indentation of when in a case/when/[else/]end. -Style/CaseIndentation: - Enabled: true - # Checks for uses of character literals. Style/CharacterLiteral: Enabled: true @@ -142,10 +339,6 @@ Style/ColonMethodCall: Style/CommentAnnotation: Enabled: false -# Indentation of comments. -Style/CommentIndentation: - Enabled: true - # Check for `if` and `case` statements where each branch is used for # assignment to the same variable when using the return of the # condition can be used instead. @@ -164,57 +357,16 @@ Style/DefWithParentheses: Style/Documentation: Enabled: false -# Multi-line method chaining should be done with leading dots. -Style/DotPosition: - Enabled: true - EnforcedStyle: leading - # This cop checks for uses of double negation (!!) to convert something # to a boolean value. As this is both cryptic and usually redundant, it # should be avoided. Style/DoubleNegation: Enabled: false -# Align elses and elsifs correctly. -Style/ElseAlignment: - Enabled: true - -# Use empty lines between defs. -Style/EmptyLineBetweenDefs: - Enabled: true - -# Don't use several empty lines in a row. -Style/EmptyLines: - Enabled: true - -# Keep blank lines around access modifiers. -Style/EmptyLinesAroundAccessModifier: - Enabled: true - -# Keeps track of empty lines around block bodies. -Style/EmptyLinesAroundBlockBody: - Enabled: true - -# Keeps track of empty lines around class bodies. -Style/EmptyLinesAroundClassBody: - Enabled: true - -# Keeps track of empty lines around method bodies. -Style/EmptyLinesAroundMethodBody: - Enabled: true - -# Keeps track of empty lines around module bodies. -Style/EmptyLinesAroundModuleBody: - Enabled: true - # Avoid the use of END blocks. Style/EndBlock: Enabled: true -# Use Unix-style line endings. -Style/EndOfLine: - Enabled: true - # Favor the use of Fixnum#even? && Fixnum#odd? Style/EvenOdd: Enabled: true @@ -223,11 +375,6 @@ Style/EvenOdd: Style/FileName: Enabled: true -# Checks for a line break before the first parameter in a multi-line method -# parameter definition. -Style/FirstMethodParameterLineBreak: - Enabled: true - # Checks for flip flops. Style/FlipFlop: Enabled: true @@ -236,6 +383,10 @@ Style/FlipFlop: Style/For: Enabled: true +# Use a consistent style for format string tokens. +Style/FormatStringToken: + Enabled: false + # Checks if there is a magic comment to enforce string literals Style/FrozenStringLiteralComment: Enabled: false @@ -261,31 +412,19 @@ Style/IdenticalConditionalBranches: Style/IfWithSemicolon: Enabled: true -# Checks the indentation of the first line of the right-hand-side of a -# multi-line assignment. -Style/IndentAssignment: - Enabled: true - -# Keep indentation straight. -Style/IndentationConsistency: - Enabled: true - -# Use 2 spaces for indentation. -Style/IndentationWidth: - Enabled: true - # Use Kernel#loop for infinite loops. Style/InfiniteLoop: Enabled: true +# Use the inverse method instead of `!.method` +# if an inverse method is defined. +Style/InverseMethods: + Enabled: false + # Use lambda.call(...) instead of lambda.(...). Style/LambdaCall: Enabled: true -# Comments should start with a space. -Style/LeadingCommentSpace: - Enabled: true - # Checks if the method definitions have or don't have parentheses. Style/MethodDefParentheses: Enabled: true @@ -298,55 +437,23 @@ Style/MethodName: Style/ModuleFunction: Enabled: false -# Checks that the closing brace in an array literal is either on the same line -# as the last array element, or a new line. -Style/MultilineArrayBraceLayout: - Enabled: true - EnforcedStyle: symmetrical - # Avoid multi-line chains of blocks. Style/MultilineBlockChain: Enabled: true -# Ensures newlines after multiline block do statements. -Style/MultilineBlockLayout: - Enabled: true - -# Checks that the closing brace in a hash literal is either on the same line as -# the last hash element, or a new line. -Style/MultilineHashBraceLayout: - Enabled: true - EnforcedStyle: symmetrical - # Do not use then for multi-line if/unless. Style/MultilineIfThen: Enabled: true -# Checks that the closing brace in a method call is either on the same line as -# the last method argument, or a new line. -Style/MultilineMethodCallBraceLayout: - Enabled: false - EnforcedStyle: symmetrical - -# Checks indentation of method calls with the dot operator that span more than -# one line. -Style/MultilineMethodCallIndentation: - Enabled: false - -# Checks that the closing brace in a method definition is symmetrical with -# respect to the opening brace and the method parameters. -Style/MultilineMethodDefinitionBraceLayout: - Enabled: false - -# Checks indentation of binary operations that span more than one line. -Style/MultilineOperationIndentation: - Enabled: true - EnforcedStyle: indented - # Avoid multi-line `? :` (the ternary operator), use if/unless instead. Style/MultilineTernaryOperator: Enabled: true +# Avoid comparing a variable with multiple items in a conditional, +# use Array#include? instead. +Style/MultipleComparison: + Enabled: false + # This cop checks whether some constant value isn't a # mutable literal (e.g. array or hash). Style/MutableConstant: @@ -421,68 +528,6 @@ Style/SignalException: EnforcedStyle: only_raise Enabled: true -# Use spaces after colons. -Style/SpaceAfterColon: - Enabled: true - -# Use spaces after commas. -Style/SpaceAfterComma: - Enabled: true - -# Do not put a space between a method name and the opening parenthesis in a -# method definition. -Style/SpaceAfterMethodName: - Enabled: true - -# Tracks redundant space after the ! operator. -Style/SpaceAfterNot: - Enabled: true - -# Use spaces after semicolons. -Style/SpaceAfterSemicolon: - Enabled: true - -# Use space around equals in parameter default -Style/SpaceAroundEqualsInParameterDefault: - Enabled: true - -# Use a space around keywords if appropriate. -Style/SpaceAroundKeyword: - Enabled: true - -# Use a single space around operators. -Style/SpaceAroundOperators: - Enabled: true - -# No spaces before commas. -Style/SpaceBeforeComma: - Enabled: true - -# Checks for missing space between code and a comment on the same line. -Style/SpaceBeforeComment: - Enabled: true - -# No spaces before semicolons. -Style/SpaceBeforeSemicolon: - Enabled: true - -# Checks for spaces inside square brackets. -Style/SpaceInsideBrackets: - Enabled: true - -# Use spaces inside hash literal braces - or don't. -Style/SpaceInsideHashLiteralBraces: - Enabled: true - -# No spaces inside range literals. -Style/SpaceInsideRangeLiteral: - Enabled: true - -# Checks for padding/surrounding spaces inside string interpolation. -Style/SpaceInsideStringInterpolation: - EnforcedStyle: no_space - Enabled: true - # Check for the usage of parentheses around stabby lambda arguments. Style/StabbyLambdaParentheses: EnforcedStyle: require_parentheses @@ -498,13 +543,9 @@ Style/StringMethods: intern: to_sym Enabled: true -# No hard tabs. -Style/Tab: - Enabled: true - -# Checks trailing blank lines and final newline. -Style/TrailingBlankLines: - Enabled: true +# Use %i or %I for arrays of symbols. +Style/SymbolArray: + Enabled: false # This cop checks for trailing comma in array and hash literals. Style/TrailingCommaInLiteral: @@ -553,6 +594,10 @@ Style/WhileUntilModifier: Style/WordArray: Enabled: true +# Do not use literals as the first operand of a comparison. +Style/YodaCondition: + Enabled: false + # Use `proc` instead of `Proc.new`. Style/Proc: Enabled: true @@ -608,6 +653,11 @@ Metrics/PerceivedComplexity: # Lint ######################################################################## +# Checks for ambiguous block association with method when param passed without +# parentheses. +Lint/AmbiguousBlockAssociation: + Enabled: false + # Checks for ambiguous operators in the first argument of a method invocation # without parentheses. Lint/AmbiguousOperator: @@ -809,6 +859,10 @@ Lint/Void: # Performance ################################################################# +# Use `caller(n..n)` instead of `caller`. +Performance/Caller: + Enabled: false + # Use `casecmp` rather than `downcase ==`. Performance/Casecmp: Enabled: true @@ -883,14 +937,23 @@ Rails/ActionFilter: Enabled: true EnforcedStyle: action +# Check that models subclass ApplicationRecord. +Rails/ApplicationRecord: + Enabled: false + +# Enforce using `blank?` and `present?`. +Rails/Blank: + Enabled: false + # Checks the correct usage of date aware methods, such as `Date.today`, # `Date.current`, etc. Rails/Date: Enabled: false # Prefer delegate method for delegations. +# Disabled per https://gitlab.com/gitlab-org/gitlab-ce/issues/35869 Rails/Delegate: - Enabled: true + Enabled: false # This cop checks dynamic `find_by_*` methods. Rails/DynamicFindBy: @@ -939,10 +1002,18 @@ Rails/OutputSafety: Rails/PluralizationGrammar: Enabled: true +# Enforce using `blank?` and `present?`. +Rails/Present: + Enabled: false + # Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`. Rails/ReadWriteAttribute: Enabled: false +# Do not assign relative date to constants. +Rails/RelativeDateConstant: + Enabled: false + # Checks the arguments of ActiveRecord scopes. Rails/ScopeArgs: Enabled: true @@ -1093,3 +1164,35 @@ RSpec/SubjectStub: # Prefer using verifying doubles over normal doubles. RSpec/VerifiedDoubles: Enabled: false + +# GitlabSecurity ############################################################## + +GitlabSecurity/DeepMunge: + Enabled: true + Exclude: + - 'spec/**/*' + - 'lib/**/*.rake' + +GitlabSecurity/PublicSend: + Enabled: true + Exclude: + - 'spec/**/*' + - 'lib/**/*.rake' + +GitlabSecurity/RedirectToParamsUpdate: + Enabled: true + Exclude: + - 'spec/**/*' + - 'lib/**/*.rake' + +GitlabSecurity/SqlInjection: + Enabled: true + Exclude: + - 'spec/**/*' + - 'lib/**/*.rake' + +GitlabSecurity/SystemCommandInjection: + Enabled: true + Exclude: + - 'spec/**/*' + - 'lib/**/*.rake' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2ec558e274f..78ab7f7204f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,26 +1,82 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 0` -# on 2017-04-07 20:17:35 -0400 using RuboCop version 0.47.1. +# on 2017-07-10 01:48:30 +0900 using RuboCop version 0.49.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 233 +# Offense count: 181 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Enabled: false + +# Offense count: 119 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Layout/IndentArray: + Enabled: false + +# Offense count: 208 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Layout/IndentHash: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Layout/SpaceBeforeFirstArg: + Enabled: false + +# Offense count: 64 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: require_no_space, require_space +Layout/SpaceInLambdaLiteral: + Enabled: false + +# Offense count: 256 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideBlockBraces: + Enabled: false + +# Offense count: 135 +# Cop supports --auto-correct. +Layout/SpaceInsideParens: + Enabled: false + +# Offense count: 14 +# Cop supports --auto-correct. +Layout/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +# Offense count: 89 +# Cop supports --auto-correct. +Layout/TrailingWhitespace: + Enabled: false + +# Offense count: 272 RSpec/EmptyLineAfterFinalLet: Enabled: false -# Offense count: 167 +# Offense count: 181 RSpec/EmptyLineAfterSubject: Enabled: false -# Offense count: 72 +# Offense count: 78 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: implicit, each, example RSpec/HookArgument: Enabled: false -# Offense count: 11 +# Offense count: 9 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: it_behaves_like, it_should_behave_like RSpec/ItBehavesLike: @@ -30,19 +86,19 @@ RSpec/ItBehavesLike: RSpec/IteratedExpectation: Enabled: false -# Offense count: 3 +# Offense count: 2 RSpec/OverwritingSetup: Enabled: false -# Offense count: 34 +# Offense count: 36 RSpec/RepeatedExample: Enabled: false -# Offense count: 43 +# Offense count: 86 RSpec/ScatteredLet: Enabled: false -# Offense count: 32 +# Offense count: 20 RSpec/ScatteredSetup: Enabled: false @@ -50,7 +106,7 @@ RSpec/ScatteredSetup: RSpec/SharedContext: Enabled: false -# Offense count: 150 +# Offense count: 115 Rails/FilePath: Enabled: false @@ -60,90 +116,71 @@ Rails/FilePath: Rails/ReversibleMigration: Enabled: false -# Offense count: 302 +# Offense count: 336 # Configuration parameters: Blacklist. # Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters Rails/SkipsModelValidations: Enabled: false -# Offense count: 7 +# Offense count: 11 # Cop supports --auto-correct. Security/YAMLLoad: Enabled: false -# Offense count: 59 +# Offense count: 58 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: Enabled: false -# Offense count: 5 +# Offense count: 6 # Cop supports --auto-correct. Style/EachWithObject: Enabled: false -# Offense count: 28 +# Offense count: 31 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty, nil, both Style/EmptyElse: Enabled: false -# Offense count: 4 +# Offense count: 9 # Cop supports --auto-correct. Style/EmptyLiteral: Enabled: false -# Offense count: 59 +# Offense count: 78 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, expanded Style/EmptyMethod: Enabled: false -# Offense count: 214 +# Offense count: 23 # Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. -Style/ExtraSpacing: - Enabled: false - -# Offense count: 9 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: format, sprintf, percent Style/FormatString: Enabled: false -# Offense count: 285 +# Offense count: 301 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 16 +# Offense count: 18 Style/IfInsideElse: Enabled: false -# Offense count: 186 +# Offense count: 182 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: Enabled: false -# Offense count: 99 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_brackets -Style/IndentArray: - Enabled: false - -# Offense count: 160 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Style/IndentHash: - Enabled: false - -# Offense count: 50 +# Offense count: 52 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: line_count_dependent, lambda, literal @@ -155,63 +192,63 @@ Style/Lambda: Style/LineEndConcatenation: Enabled: false -# Offense count: 34 +# Offense count: 40 # Cop supports --auto-correct. Style/MethodCallWithoutArgsParentheses: Enabled: false -# Offense count: 10 +# Offense count: 13 Style/MethodMissing: Enabled: false -# Offense count: 3 +# Offense count: 6 # Cop supports --auto-correct. Style/MultilineIfModifier: Enabled: false -# Offense count: 24 +# Offense count: 26 # Cop supports --auto-correct. Style/NestedParenthesizedCalls: Enabled: false -# Offense count: 18 +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. # SupportedStyles: skip_modifier_ifs, always Style/Next: Enabled: false -# Offense count: 37 +# Offense count: 45 # Cop supports --auto-correct. # Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles. # SupportedOctalStyles: zero_with_o, zero_only Style/NumericLiteralPrefix: Enabled: false -# Offense count: 88 +# Offense count: 98 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. # SupportedStyles: predicate, comparison Style/NumericPredicate: Enabled: false -# Offense count: 36 +# Offense count: 42 # Cop supports --auto-correct. Style/ParallelAssignment: Enabled: false -# Offense count: 570 +# Offense count: 800 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: Enabled: false -# Offense count: 14 +# Offense count: 15 # Cop supports --auto-correct. Style/PerlBackrefs: Enabled: false -# Offense count: 83 +# Offense count: 105 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -219,47 +256,47 @@ Style/PerlBackrefs: Style/PredicateName: Enabled: false -# Offense count: 65 +# Offense count: 58 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, exploded Style/RaiseArgs: Enabled: false -# Offense count: 5 +# Offense count: 6 # Cop supports --auto-correct. Style/RedundantBegin: Enabled: false -# Offense count: 32 +# Offense count: 37 # Cop supports --auto-correct. Style/RedundantFreeze: Enabled: false -# Offense count: 15 +# Offense count: 14 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Enabled: false -# Offense count: 382 +# Offense count: 406 # Cop supports --auto-correct. Style/RedundantSelf: Enabled: false -# Offense count: 111 +# Offense count: 115 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Enabled: false -# Offense count: 24 +# Offense count: 29 # Cop supports --auto-correct. Style/RescueModifier: Enabled: false -# Offense count: 7 +# Offense count: 8 # Cop supports --auto-correct. Style/SelfAssignment: Enabled: false @@ -270,101 +307,58 @@ Style/SelfAssignment: Style/SingleLineMethods: Enabled: false -# Offense count: 168 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: space, no_space -Style/SpaceBeforeBlockBraces: - Enabled: false - -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Style/SpaceBeforeFirstArg: - Enabled: false - -# Offense count: 46 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: require_no_space, require_space -Style/SpaceInLambdaLiteral: - Enabled: false - -# Offense count: 229 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Style/SpaceInsideBlockBraces: - Enabled: false - -# Offense count: 116 -# Cop supports --auto-correct. -Style/SpaceInsideParens: - Enabled: false - -# Offense count: 12 -# Cop supports --auto-correct. -Style/SpaceInsidePercentLiteralDelimiters: - Enabled: false - -# Offense count: 57 +# Offense count: 64 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles. # SupportedStyles: use_perl_names, use_english_names Style/SpecialGlobalVars: EnforcedStyle: use_perl_names -# Offense count: 42 +# Offense count: 44 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: single_quotes, double_quotes Style/StringLiteralsInInterpolation: Enabled: false -# Offense count: 64 +# Offense count: 84 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Enabled: false -# Offense count: 6 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex Style/TernaryParentheses: Enabled: false -# Offense count: 18 +# Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: AllowNamedUnderscoreVariables. Style/TrailingUnderscoreVariable: Enabled: false -# Offense count: 78 -# Cop supports --auto-correct. -Style/TrailingWhitespace: - Enabled: false - -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist. # Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym Style/TrivialAccessors: Enabled: false -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. Style/UnlessElse: Enabled: false -# Offense count: 24 +# Offense count: 28 # Cop supports --auto-correct. Style/UnneededInterpolation: Enabled: false -# Offense count: 8 +# Offense count: 11 # Cop supports --auto-correct. Style/ZeroLengthPredicate: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 706823ed693..7493f2562e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1708,8 +1708,6 @@ entry. ## 8.16.7 (2017-02-27) -- No changes. -- No changes. - Fix MR changes tab size count when there are over 100 files in the diff. ## 8.16.6 (2017-02-17) @@ -1923,6 +1921,14 @@ entry. - Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects. - Patch XSS vulnerability in RDOC support. +## 8.15.5 (2017-01-20) + +- Ensure export files are removed after a namespace is deleted. +- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling) +- Prevent users from creating notes on resources they can't access. +- Prevent users from deleting system deploy keys via the project deploy key API. +- Upgrade omniauth gem to 1.3.2. + ## 8.15.4 (2017-01-09) - Make successful pipeline emails off for watchers. !8176 @@ -2205,6 +2211,14 @@ entry. - Speed up group milestone index by passing group_id to IssuesFinder. !8363 - Ensure issuable state changes only fire webhooks once. +## 8.14.7 (2017-01-21) + +- Ensure export files are removed after a namespace is deleted. +- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling) +- Prevent users from creating notes on resources they can't access. +- Prevent users from deleting system deploy keys via the project deploy key API. +- Upgrade omniauth gem to 1.3.2. + ## 8.14.6 (2017-01-10) - Update the gitlab-markup gem to the version 1.5.1. !8509 @@ -2487,6 +2501,14 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.12 (2017-01-21) + +- Ensure export files are removed after a namespace is deleted. +- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling) +- Prevent users from creating notes on resources they can't access. +- Prevent users from deleting system deploy keys via the project deploy key API. +- Upgrade omniauth gem to 1.3.2. + ## 8.13.11 (2017-01-10) - Update the gitlab-markup gem to the version 1.5.1. !8509 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index d21d277be51..ae6dd4e2032 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.25.0 +0.29.0 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 8a30e8f94a3..11d9efa3d5a 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -5.4.0 +5.8.0 @@ -314,11 +314,11 @@ group :development, :test do gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', '~> 1.2.0', require: false - gem 'fuubar', '~> 2.0.0' + gem 'fuubar', '~> 2.2.0' gem 'database_cleaner', '~> 1.5.0' gem 'factory_girl_rails', '~> 4.7.0' - gem 'rspec-rails', '~> 3.5.0' + gem 'rspec-rails', '~> 3.6.0' gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -339,10 +339,11 @@ group :development, :test do gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-spinach', '~> 1.1.0' - gem 'rubocop', '~> 0.47.1', require: false - gem 'rubocop-rspec', '~> 1.15.0', require: false + gem 'rubocop', '~> 0.49.1', require: false + gem 'rubocop-rspec', '~> 1.15.1', require: false + gem 'rubocop-gitlab-security', '~> 0.0.6', require: false gem 'scss_lint', '~> 0.54.0', require: false - gem 'haml_lint', '~> 0.21.0', require: false + gem 'haml_lint', '~> 0.26.0', require: false gem 'simplecov', '~> 0.14.0', require: false gem 'flay', '~> 2.8.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false @@ -354,7 +355,7 @@ group :development, :test do gem 'activerecord_sane_schema_dumper', '0.2' - gem 'stackprof', '~> 0.2.10' + gem 'stackprof', '~> 0.2.10', require: false end group :test do @@ -390,8 +391,18 @@ gem 'health_check', '~> 2.6.0' gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' +# SSH host key support +gem 'net-ssh', '~> 4.1.0' + +# Required for ED25519 SSH host key support +group :ed25519 do + gem 'rbnacl-libsodium' + gem 'rbnacl', '~> 3.2' + gem 'bcrypt_pbkdf', '~> 1.0' +end + # Gitaly GRPC client -gem 'gitaly', '~> 0.21.0' +gem 'gitaly', '~> 0.26.0' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 61d8b23d9fc..cb75ffc7761 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,7 @@ GEM babosa (1.0.2) base32 (0.3.2) bcrypt (3.1.11) + bcrypt_pbkdf (1.0.0) benchmark-ips (2.3.0) better_errors (2.1.1) coderay (>= 1.0.0) @@ -157,7 +158,7 @@ GEM devise (~> 4.0) railties rotp (~> 2.0) - diff-lcs (1.2.5) + diff-lcs (1.3) diffy (3.1.0) docile (1.1.5) domain_name (0.5.20161021) @@ -251,8 +252,8 @@ GEM foreman (0.78.0) thor (~> 0.19.1) formatador (0.2.5) - fuubar (2.0.0) - rspec (~> 3.0) + fuubar (2.2.0) + rspec-core (~> 3.0) ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) @@ -270,7 +271,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly (0.21.0) + gitaly (0.26.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -357,10 +358,11 @@ GEM googleauth (~> 0.5.1) haml (4.0.7) tilt - haml_lint (0.21.0) - haml (~> 4.0) + haml_lint (0.26.0) + haml (>= 4.0, < 5.1) + rainbow rake (>= 10, < 13) - rubocop (>= 0.47.0) + rubocop (>= 0.49.0) sysexits (~> 1.1) hamlit (2.6.1) temple (~> 0.7.6) @@ -394,7 +396,7 @@ GEM json (~> 1.8) multi_xml (>= 0.5.2) httpclient (2.8.2) - i18n (0.8.1) + i18n (0.8.6) ice_nine (0.11.2) influxdb (0.2.3) cause @@ -475,6 +477,7 @@ GEM mustermann (~> 1.0.0) mysql2 (0.4.5) net-ldap (0.16.0) + net-ssh (4.1.0) netrc (0.11.0) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) @@ -546,6 +549,7 @@ GEM rubypants (~> 0.2) orm_adapter (0.5.0) os (0.9.6) + parallel (1.12.0) paranoia (2.3.1) activerecord (>= 4.0, < 5.2) parser (2.4.0.0) @@ -605,7 +609,7 @@ GEM pry-rails (0.3.5) pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) - rack (1.6.5) + rack (1.6.8) rack-accept (0.4.5) rack (>= 0.4) rack-attack (4.4.1) @@ -653,9 +657,13 @@ GEM rainbow (2.2.2) rake raindrops (0.18.0) - rake (10.5.0) + rake (12.0.0) rblineprof (0.3.6) debugger-ruby_core_source (~> 1.3) + rbnacl (3.4.0) + ffi + rbnacl-libsodium (1.0.11) + rbnacl (>= 3.0.1) rdoc (4.2.2) json (~> 1.4) re2 (1.1.1) @@ -697,43 +705,41 @@ GEM chunky_png rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.0) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-mocks (3.5.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-rails (3.5.0) + rspec-support (~> 3.6.0) + rspec-rails (3.6.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-support (~> 3.5.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-support (~> 3.6.0) rspec-retry (0.4.5) rspec-core rspec-set (0.1.3) - rspec-support (3.5.0) + rspec-support (3.6.0) rspec_profiling (0.0.5) activerecord pg rails sqlite3 - rubocop (0.47.1) + rubocop (0.49.1) + parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-rspec (1.15.0) - rubocop (>= 0.42.0) + rubocop-gitlab-security (0.0.6) + rubocop (>= 0.47.1) + rubocop-rspec (1.15.1) ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-prof (0.16.2) @@ -858,7 +864,7 @@ GEM truncato (0.7.8) htmlentities (~> 4.3.1) nokogiri (~> 1.6.1) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) u2f (0.2.1) uglifier (2.7.2) @@ -868,7 +874,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.1.3) + unicode-display_width (1.3.0) unicorn (5.1.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -923,6 +929,7 @@ DEPENDENCIES awesome_print (~> 1.2.0) babosa (~> 1.0.2) base32 (~> 0.3.0) + bcrypt_pbkdf (~> 1.0) benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) @@ -968,13 +975,13 @@ DEPENDENCIES fog-rackspace (~> 0.1.1) font-awesome-rails (~> 4.7) foreman (~> 0.78.0) - fuubar (~> 2.0.0) + fuubar (~> 2.2.0) gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly (~> 0.21.0) + gitaly (~> 0.26.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.1) @@ -987,7 +994,7 @@ DEPENDENCIES grape (~> 0.19.2) grape-entity (~> 0.6.0) grape-route-helpers (~> 2.0.0) - haml_lint (~> 0.21.0) + haml_lint (~> 0.26.0) hamlit (~> 2.6.1) hashie-forbidden_attributes health_check (~> 2.6.0) @@ -1015,6 +1022,7 @@ DEPENDENCIES mousetrap-rails (~> 1.4.6) mysql2 (~> 0.4.5) net-ldap + net-ssh (~> 4.1.0) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.4) octokit (~> 4.6.2) @@ -1060,6 +1068,8 @@ DEPENDENCIES rainbow (~> 2.2) raindrops (~> 0.18) rblineprof (~> 0.3.6) + rbnacl (~> 3.2) + rbnacl-libsodium rdoc (~> 4.2) re2 (~> 1.1.1) recaptcha (~> 3.0) @@ -1071,12 +1081,13 @@ DEPENDENCIES responders (~> 2.0) rouge (~> 2.0) rqrcode-rails3 (~> 0.1.7) - rspec-rails (~> 3.5.0) + rspec-rails (~> 3.6.0) rspec-retry (~> 0.4.5) rspec-set (~> 0.1.3) rspec_profiling (~> 0.0.5) - rubocop (~> 0.47.1) - rubocop-rspec (~> 1.15.0) + rubocop (~> 0.49.1) + rubocop-gitlab-security (~> 0.0.6) + rubocop-rspec (~> 1.15.1) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.16.2) ruby_parser (~> 3.8) @@ -1126,4 +1137,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/PROCESS.md b/PROCESS.md index 2b3d142bf77..e5b17784d20 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -119,6 +119,12 @@ only be left until after the freeze if: are aware of it. * It is in the correct milestone, with the ~Deliverable label. +If a merge request is not ready, but the developers and Product Manager +responsible for the feature think it is essential that it is in the release, +they can [ask for an exception](#asking-for-an-exception) in advance. This is +preferable to merging something that we are not confident in, but should still +be a rare case: most features can be allowed to slip a release. + All Community Edition merge requests from GitLab team members merged on the freeze date (the 7th) should have a corresponding Enterprise Edition merge request, even if there are no conflicts. This is to reduce the size of the @@ -128,11 +134,26 @@ information, see ### After the 7th -Once the stable branch is frozen, only fixes for [regressions](#regressions) -and security issues will be cherry-picked into the stable branch. -Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. -These fixes will be shipped in the next RC for that release if it is before the 22nd. -If the fixes are are completed on or after the 22nd, they will be shipped in a patch for that release. +Once the stable branch is frozen, the only MRs that can be cherry-picked into +the stable branch are: + +* Fixes for [regressions](#regressions) +* Fixes for security issues +* New or updated translations (as long as they do not touch application code) + +Any merge requests cherry-picked into the stable branch for a previous release +will also be picked into the latest stable branch. These fixes will be shipped +in the next RC for that release if it is before the 22nd. If the fixes are are +completed on or after the 22nd, they will be shipped in a patch for that +release. + +During the feature freeze all merge requests that are meant to go into the upcoming +release should have the correct milestone assigned _and_ have the label +~"Pick into Stable" set, so that release managers can find and pick them. +Merge requests without a milestone and this label will +not be merged into any stable branches. + +### Asking for an exception If you think a merge request should go into an RC or patch even though it does not meet these requirements, you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer: @@ -152,11 +173,7 @@ When in doubt, we err on the side of _not_ cherry-picking. For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement (e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested. -During the feature freeze all merge requests that are meant to go into the upcoming -release should have the correct milestone assigned _and_ have the label -~"Pick into Stable" set, so that release managers can find and pick them. -Merge requests without a milestone and this label will -not be merged into any stable branches. +All MRs which have had exceptions granted must be merged by the 15th. ### Regressions diff --git a/app/assets/images/new_repo.png b/app/assets/images/new_repo.png Binary files differnew file mode 100644 index 00000000000..ed3af06ab1d --- /dev/null +++ b/app/assets/images/new_repo.png diff --git a/app/assets/images/old_repo.png b/app/assets/images/old_repo.png Binary files differnew file mode 100644 index 00000000000..c3c3b791ad9 --- /dev/null +++ b/app/assets/images/old_repo.png diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js index 38a8317dbd7..8f5e2e545ec 100644 --- a/app/assets/javascripts/ajax_loading_spinner.js +++ b/app/assets/javascripts/ajax_loading_spinner.js @@ -10,7 +10,7 @@ class AjaxLoadingSpinner { e.target.setAttribute('disabled', ''); const iconElement = e.target.querySelector('i'); // get first fa- icon - const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first(); + const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0]; iconElement.dataset.icon = originalIcon; AjaxLoadingSpinner.toggleLoadingIcon(iconElement); $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend); diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 56fa0d71a9a..76b724e1bcb 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -13,6 +13,7 @@ const Api = { dockerfilePath: '/api/:version/templates/dockerfiles/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', usersPath: '/api/:version/users.json', + commitPath: '/api/:version/projects/:id/repository/commits', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath) @@ -95,6 +96,21 @@ const Api = { .done(projects => callback(projects)); }, + commitMultiple(id, data, callback) { + // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions + const url = Api.buildUrl(Api.commitPath) + .replace(':id', id); + return $.ajax({ + url, + type: 'POST', + contentType: 'application/json; charset=utf-8', + data: JSON.stringify(data), + dataType: 'json', + }) + .done(commitData => callback(commitData)) + .fail(message => callback(message.responseJSON)); + }, + // Return text for a specific license licenseText(key, data, callback) { const url = Api.buildUrl(Api.licensePath) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 18cd04b176a..097f79a250a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this */ /* global Flash */ - +import _ from 'underscore'; import Cookies from 'js-cookie'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index b20d108aa25..035a7e5c431 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import '../commons/bootstrap'; // Requires Input behavior @@ -48,7 +49,9 @@ function hideOrShowHelpBlock(form) { $(() => { const $form = $('form.js-requires-input'); - $form.requiresInput(); - hideOrShowHelpBlock($form); - $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + if ($form) { + $form.requiresInput(); + hideOrShowHelpBlock($form); + $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + } }); diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 77e92ff8caf..b70b0a9bbf8 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,4 +1,3 @@ - // Toggle button. Show/hide content inside parent container. // Button does not change visibility. If button has icon - it changes chevron style. // diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index dc636050221..26d3419a162 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,9 +1,24 @@ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ /* global Dropzone */ +import '../lib/utils/url_utility'; +import { HIDDEN_CLASS } from '../lib/utils/constants'; + +function toggleLoading($el, $icon, loading) { + if (loading) { + $el.disable(); + $icon.removeClass(HIDDEN_CLASS); + } else { + $el.enable(); + $icon.addClass(HIDDEN_CLASS); + } +} export default class BlobFileDropzone { constructor(form, method) { const formDropzone = form.find('.dropzone'); + const submitButton = form.find('#submit-all'); + const submitButtonLoadingIcon = submitButton.find('.js-loading-icon'); + const dropzoneMessage = form.find('.dz-message'); Dropzone.autoDiscover = false; const dropzone = formDropzone.dropzone({ @@ -26,12 +41,20 @@ export default class BlobFileDropzone { }, init: function () { this.on('addedfile', function () { + toggleLoading(submitButton, submitButtonLoadingIcon, false); + dropzoneMessage.addClass(HIDDEN_CLASS); $('.dropzone-alerts').html('').hide(); }); + this.on('removedfile', function () { + toggleLoading(submitButton, submitButtonLoadingIcon, false); + dropzoneMessage.removeClass(HIDDEN_CLASS); + }); this.on('success', function (header, response) { - window.location.href = response.filePath; + $('#modal-upload-blob').modal('hide'); + window.gl.utils.visitUrl(response.filePath); }); this.on('maxfilesexceeded', function (file) { + dropzoneMessage.addClass(HIDDEN_CLASS); this.removeFile(file); }); this.on('sending', function (file, xhr, formData) { @@ -48,14 +71,15 @@ export default class BlobFileDropzone { }, }); - const submitButton = form.find('#submit-all')[0]; - submitButton.addEventListener('click', function (e) { + submitButton.on('click', (e) => { e.preventDefault(); e.stopPropagation(); if (dropzone[0].dropzone.getQueuedFiles().length === 0) { // eslint-disable-next-line no-alert alert('Please select a file'); + return false; } + toggleLoading(submitButton, submitButtonLoadingIcon, true); dropzone[0].dropzone.processQueue(); return false; }); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 1c64ccf536f..b5500ac116f 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone'; $(() => { const editBlobForm = $('.js-edit-blob-form'); const uploadBlobForm = $('.js-upload-blob-form'); + const deleteBlobForm = $('.js-delete-blob-form'); if (editBlobForm.length) { const urlRoot = editBlobForm.data('relative-url-root'); @@ -30,4 +31,8 @@ $(() => { '.btn-upload-file', ); } + + if (deleteBlobForm.length) { + new NewCommitForm(deleteBlobForm); + } }); diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 88b054b76e6..89c14180149 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -2,6 +2,7 @@ /* global BoardService */ /* global Flash */ +import _ from 'underscore'; import Vue from 'vue'; import VueResource from 'vue-resource'; import FilteredSearchBoards from './filtered_search_boards'; diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index e7f16899362..edfe7c326db 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -1,5 +1,6 @@ /* global ListLabel */ +import _ from 'underscore'; import Cookies from 'js-cookie'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index daef01bc93d..d3de1830895 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -97,9 +97,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({ return `Avatar for ${assignee.name}`; }, showLabel(label) { - if (!this.list) return true; - - return !this.list.label || label.id !== this.list.label.id; + if (!this.list || !label) return true; + return true; }, filterByLabel(label, e) { if (!this.updateFilters) return; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 1d36519c75c..96af69e7a36 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -1,8 +1,8 @@ /* global ListIssue */ import Vue from 'vue'; -import queryData from '../../utils/query_data'; -import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; +import queryData from '~/boards/utils/query_data'; +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import './header'; import './list'; import './footer'; diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index f29b6caa1ac..72bb9e10fbc 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -1,5 +1,6 @@ /* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var, promise/catch-or-return */ +import _ from 'underscore'; window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 1e12d4ca415..43928e602d6 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -1,6 +1,6 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */ /* global List */ - +import _ from 'underscore'; import Cookies from 'js-cookie'; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 1dfa064acfd..940326dcd33 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -64,7 +64,7 @@ window.Build = (function () { $(window) .off('scroll') .on('scroll', () => { - const contentHeight = this.$buildTraceOutput.prop('scrollHeight'); + const contentHeight = this.$buildTraceOutput.height(); if (contentHeight > this.windowSize) { // means the user did not scroll, the content was updated. this.windowSize = contentHeight; @@ -105,16 +105,17 @@ window.Build = (function () { }; Build.prototype.canScroll = function () { - return document.body.scrollHeight > window.innerHeight; + return $(document).height() > $(window).height(); }; Build.prototype.toggleScroll = function () { - const currentPosition = document.body.scrollTop; - const windowHeight = window.innerHeight; + const currentPosition = $(document).scrollTop(); + const scrollHeight = $(document).height(); + const windowHeight = $(window).height(); if (this.canScroll()) { if (currentPosition > 0 && - (document.body.scrollHeight - currentPosition !== windowHeight)) { + (scrollHeight - currentPosition !== windowHeight)) { // User is in the middle of the log this.toggleDisableButton(this.$scrollTopBtn, false); @@ -124,7 +125,7 @@ window.Build = (function () { this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, false); - } else if (document.body.scrollHeight - currentPosition === windowHeight) { + } else if (scrollHeight - currentPosition === windowHeight) { // User is at the bottom of the build log. this.toggleDisableButton(this.$scrollTopBtn, false); @@ -137,7 +138,7 @@ window.Build = (function () { }; Build.prototype.scrollDown = function () { - document.body.scrollTop = document.body.scrollHeight; + $(document).scrollTop($(document).height()); }; Build.prototype.scrollToBottom = function () { @@ -147,7 +148,7 @@ window.Build = (function () { }; Build.prototype.scrollToTop = function () { - document.body.scrollTop = 0; + $(document).scrollTop(0); this.hasBeenScrolled = true; this.toggleScroll(); }; @@ -163,7 +164,6 @@ window.Build = (function () { Build.prototype.initSidebar = function () { this.$sidebar = $('.js-build-sidebar'); - this.$sidebar.niceScroll(); }; Build.prototype.getBuildTrace = function () { @@ -178,7 +178,7 @@ window.Build = (function () { this.state = log.state; } - this.windowSize = this.$buildTraceOutput.prop('scrollHeight'); + this.windowSize = this.$buildTraceOutput.height(); if (log.append) { this.$buildTraceOutput.append(log.html); diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js index 99082b412e2..c955a9ac2ea 100644 --- a/app/assets/javascripts/build_variables.js +++ b/app/assets/javascripts/build_variables.js @@ -2,7 +2,7 @@ $(function() { $('.reveal-variables').off('click').on('click', function() { - $('.js-build').toggle().niceScroll(); + $('.js-build-variables').toggle(); $(this).hide(); }); }); diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index 510bedbf641..c11b7d5f340 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -3,6 +3,7 @@ import $ from 'jquery'; // bootstrap jQuery plugins import 'bootstrap-sass/assets/javascripts/bootstrap/affix'; import 'bootstrap-sass/assets/javascripts/bootstrap/alert'; +import 'bootstrap-sass/assets/javascripts/bootstrap/button'; import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown'; import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js index 7063f59d446..6db8b3afbef 100644 --- a/app/assets/javascripts/commons/index.js +++ b/app/assets/javascripts/commons/index.js @@ -1,3 +1,4 @@ +import 'underscore'; import './polyfills'; import './jquery'; import './bootstrap'; diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js index b53f6284afc..b93e94a3c97 100644 --- a/app/assets/javascripts/commons/jquery.js +++ b/app/assets/javascripts/commons/jquery.js @@ -6,6 +6,5 @@ import 'vendor/jquery.endless-scroll'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; import 'vendor/jquery.scrollTo'; -import 'vendor/jquery.nicescroll'; import 'vendor/jquery.waitforimages'; import 'select2/select2'; diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js index 54257531284..13ba4a57293 100644 --- a/app/assets/javascripts/copy_as_gfm.js +++ b/app/assets/javascripts/copy_as_gfm.js @@ -1,5 +1,5 @@ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ - +import _ from 'underscore'; import './lib/utils/common_utils'; import { placeholderImage } from './lazy_loader'; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 44791a93936..6583e471a48 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -92,7 +92,7 @@ $(() => { }); }, selectDefaultStage() { - const stage = this.state.stages.first(); + const stage = this.state.stages[0]; this.selectStage(stage); }, selectStage(stage) { diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 37ddca29e71..298f737a2bc 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({ hasDiscussionsToJumpTo = false; } } - } else if (activeTab !== 'notes') { + } else if (activeTab !== 'show') { // If we are on the commits or builds tabs, // there are no discussions to jump to. hasDiscussionsToJumpTo = false; @@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({ if (!hasDiscussionsToJumpTo) { // If there are no discussions to jump to on the current page, // switch to the notes tab and jump to the first disucssion there. - window.mrTabs.activateTab('notes'); - activeTab = 'notes'; + window.mrTabs.activateTab('show'); + activeTab = 'show'; jumpToFirstDiscussion = true; } - if (activeTab === 'notes') { + if (activeTab === 'show') { discussionsSelector = '.discussion[data-discussion-id]'; discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); } @@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({ let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); - if (activeTab === 'notes') { + if (activeTab === 'show') { $target = $target.closest('.note-discussion'); // If the next discussion is closed, toggle it open. diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d5923266c60..7cc7636cca3 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -8,6 +8,7 @@ /* global LabelsSelect */ /* global MilestoneSelect */ /* global Commit */ +/* global CommitsList */ /* global NewBranchForm */ /* global NotificationsForm */ /* global NotificationsDropdown */ @@ -19,15 +20,20 @@ /* global Search */ /* global Admin */ /* global NamespaceSelects */ +/* global NewCommitForm */ +/* global NewBranchForm */ /* global Project */ /* global ProjectAvatar */ /* global MergeRequest */ /* global Compare */ /* global CompareAutocomplete */ +/* global ProjectFindFile */ /* global ProjectNew */ /* global ProjectShow */ +/* global ProjectImport */ /* global Labels */ /* global Shortcuts */ +/* global ShortcutsFindFile */ /* global Sidebar */ /* global ShortcutsWiki */ @@ -69,14 +75,11 @@ import initNotes from './init_notes'; import initLegacyFilters from './init_legacy_filters'; import initIssuableSidebar from './init_issuable_sidebar'; import GpgBadges from './gpg_badges'; +import UserFeatureHelper from './helpers/user_feature_helper'; (function() { var Dispatcher; - $(function() { - return new Dispatcher(); - }); - Dispatcher = (function() { function Dispatcher() { this.initSearch(); @@ -90,6 +93,7 @@ import GpgBadges from './gpg_badges'; if (!page) { return false; } + path = page.split(':'); shortcut_handler = null; @@ -133,6 +137,8 @@ import GpgBadges from './gpg_badges'; .init(); } + const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); + switch (page) { case 'profiles:preferences:show': initExperimentalFlags(); @@ -149,7 +155,7 @@ import GpgBadges from './gpg_badges'; break; case 'projects:merge_requests:index': case 'projects:issues:index': - if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) { + if (filteredSearchEnabled) { const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } @@ -177,11 +183,17 @@ import GpgBadges from './gpg_badges'; break; case 'dashboard:issues': case 'dashboard:merge_requests': - case 'groups:issues': case 'groups:merge_requests': new ProjectSelect(); initLegacyFilters(); break; + case 'groups:issues': + if (filteredSearchEnabled) { + const filteredSearchManager = new gl.FilteredSearchManager('issues'); + filteredSearchManager.setup(); + } + new ProjectSelect(); + break; case 'dashboard:todos:index': new Todos(); break; @@ -195,7 +207,6 @@ import GpgBadges from './gpg_badges'; break; case 'explore:groups:index': new GroupsList(); - const landingElement = document.querySelector('.js-explore-groups-landing'); if (!landingElement) break; const exploreGroupsLanding = new Landing( @@ -218,6 +229,10 @@ import GpgBadges from './gpg_badges'; case 'projects:compare:show': new gl.Diff(); break; + case 'projects:branches:new': + case 'projects:branches:create': + new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); + break; case 'projects:branches:index': gl.AjaxLoadingSpinner.init(); new DeleteModal(); @@ -305,31 +320,38 @@ import GpgBadges from './gpg_badges'; container: '.js-commit-pipeline-graph', }).bindEvents(); initNotes(); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; case 'projects:commit:pipelines': new MiniPipelineGraph({ container: '.js-commit-pipeline-graph', }).bindEvents(); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; - case 'projects:commits:show': + case 'projects:activity': + new gl.Activities(); shortcut_handler = new ShortcutsNavigation(); - GpgBadges.fetch(); break; - case 'projects:activity': + case 'projects:commits:show': + CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); shortcut_handler = new ShortcutsNavigation(); + GpgBadges.fetch(); break; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); - if ($('#tree-slider').length) { - new TreeView(); - } - if ($('.blob-viewer').length) { - new BlobViewer(); - } + + if ($('#tree-slider').length) new TreeView(); + if ($('.blob-viewer').length) new BlobViewer(); + if ($('.project-show-activity').length) new gl.Activities(); break; case 'projects:edit': setupProjectEdit(); + // Initialize expandable settings panels + initSettingsPanels(); + break; + case 'projects:imports:show': + new ProjectImport(); break; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); @@ -385,16 +407,28 @@ import GpgBadges from './gpg_badges'; break; case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); + + if (UserFeatureHelper.isNewRepo()) break; + new TreeView(); new BlobViewer(); + new NewCommitForm($('.js-create-dir-form')); $('#tree-slider').waitForImages(function() { gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); break; case 'projects:find_file:show': + const findElement = document.querySelector('.js-file-finder'); + const projectFindFile = new ProjectFindFile($(".file-finder-holder"), { + url: findElement.dataset.fileFindUrl, + treeUrl: findElement.dataset.findTreeUrl, + blobUrlTemplate: findElement.dataset.blobUrlTemplate, + }); + new ShortcutsFindFile(projectFindFile); shortcut_handler = true; break; case 'projects:blob:show': + if (UserFeatureHelper.isNewRepo()) break; new BlobViewer(); initBlob(); break; @@ -475,7 +509,7 @@ import GpgBadges from './gpg_badges'; new gl.DueDateSelectors(); break; } - switch (path.first()) { + switch (path[0]) { case 'sessions': case 'omniauth_callbacks': if (!gon.u2f) break; @@ -547,7 +581,6 @@ import GpgBadges from './gpg_badges'; shortcut_handler = new ShortcutsWiki(); new ZenMode(); new gl.GLForm($('.wiki-form'), true); - new Sidebar(); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); @@ -604,4 +637,8 @@ import GpgBadges from './gpg_badges'; return Dispatcher; })(); + + $(function() { + new Dispatcher(); + }); }).call(window); diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js index c0da5866139..267b53fa4f2 100644 --- a/app/assets/javascripts/droplab/plugins/ajax.js +++ b/app/assets/javascripts/droplab/plugins/ajax.js @@ -11,6 +11,16 @@ const Ajax = { if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data); }, + preprocessing: function preprocessing(config, data) { + let results = data; + + if (config.preprocessing && !data.preprocessed) { + results = config.preprocessing(data); + AjaxCache.override(config.endpoint, results); + } + + return results; + }, init: function init(hook) { var self = this; self.destroyed = false; @@ -31,7 +41,8 @@ const Ajax = { dynamicList.outerHTML = loadingTemplate.outerHTML; } - AjaxCache.retrieve(config.endpoint) + return AjaxCache.retrieve(config.endpoint) + .then(self.preprocessing.bind(null, config)) .then((data) => self._loadData(data, config, self)) .catch(config.onError); }, diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 9ebbb22e807..6d19a6d9b3a 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,11 +1,10 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */ /* global Dropzone */ - +import _ from 'underscore'; import './preview_markdown'; window.DropzoneInput = (function() { function DropzoneInput(form) { - Dropzone.autoDiscover = false; const divHover = '<div class="div-dropzone-hover"></div>'; const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; const $attachButton = form.find('.button-attach-file'); @@ -218,7 +217,7 @@ window.DropzoneInput = (function() { value = e.clipboardData.getData('text/plain'); } value = value.split("\r"); - return value.first(); + return value[0]; }; const showSpinner = function(e) { diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 2856c8e2862..ee71728184f 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -1,7 +1,7 @@ /* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */ /* global dateFormat */ -/* global Pikaday */ +import Pikaday from 'pikaday'; import DateFix from './lib/utils/datefix'; class DueDateSelect { diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index cac35d6eed5..dc7672560ea 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js deleted file mode 100644 index 027222f804d..00000000000 --- a/app/assets/javascripts/extensions/array.js +++ /dev/null @@ -1,11 +0,0 @@ -// TODO: remove this - -// eslint-disable-next-line no-extend-native -Array.prototype.first = function first() { - return this[0]; -}; - -// eslint-disable-next-line no-extend-native -Array.prototype.last = function last() { - return this[this.length - 1]; -}; diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index 139206cc185..6d516a253bb 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + /** * Makes search request for content when user types a value in the search input. * Updates the html content of the page with the received one. diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index 2615d626c4c..0bc4b6f22a9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -6,7 +6,7 @@ import './filtered_search_dropdown'; class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(options = {}) { - const { input, endpoint, symbol } = options; + const { input, endpoint, symbol, preprocessing } = options; super(options); this.symbol = symbol; this.config = { @@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown { endpoint, method: 'setData', loadingTemplate: this.loadingTemplate, + preprocessing, onError() { /* eslint-disable no-new */ new Flash('An error occured fetching the dropdown data.'); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index ef8fe071012..8d711e3213c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import FilteredSearchContainer from './container'; class DropdownUtils { @@ -50,6 +51,66 @@ class DropdownUtils { return updatedItem; } + static mergeDuplicateLabels(dataMap, newLabel) { + const updatedMap = dataMap; + const key = newLabel.title; + + const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key); + + if (!hasKeyProperty) { + updatedMap[key] = newLabel; + } else { + const existing = updatedMap[key]; + + if (!existing.multipleColors) { + existing.multipleColors = [existing.color]; + } + + existing.multipleColors.push(newLabel.color); + } + + return updatedMap; + } + + static duplicateLabelColor(labelColors) { + const colors = labelColors; + const spacing = 100 / colors.length; + + // Reduce the colors to 4 + colors.length = Math.min(colors.length, 4); + + const color = colors.map((c, i) => { + const percentFirst = Math.floor(spacing * i); + const percentSecond = Math.floor(spacing * (i + 1)); + return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; + }).join(', '); + + return `linear-gradient(${color})`; + } + + static duplicateLabelPreprocessing(data) { + const results = []; + const dataMap = {}; + + data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); + + Object.keys(dataMap) + .forEach((key) => { + const label = dataMap[key]; + + if (label.multipleColors) { + label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); + label.text_color = '#000000'; + } + + results.push(label); + }); + + results.preprocessed = true; + + return results; + } + static filterHint(config, item) { const { input, allowedKeys } = config; const updatedItem = item; @@ -62,11 +123,11 @@ class DropdownUtils { if (!allowMultiple && itemInExistingTokens) { updatedItem.droplab_hidden = true; - } else if (!lastKey || searchInput.split('').last() === ' ') { + } else if (!lastKey || _.last(searchInput.split('')) === ' ') { updatedItem.droplab_hidden = false; } else if (lastKey) { const split = lastKey.split(':'); - const tokenName = split[0].split(' ').last(); + const tokenName = _.last(split[0].split(' ')); const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; updatedItem.droplab_hidden = tokenName ? match : false; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 61cef435209..dd1c067df87 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -54,6 +54,7 @@ class FilteredSearchDropdownManager { extraArguments: { endpoint: `${this.baseEndpoint}/labels.json`, symbol: '~', + preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing, }, element: this.container.querySelector('#js-dropdown-label'), }, @@ -166,7 +167,7 @@ class FilteredSearchDropdownManager { // Eg. token = 'label:' const split = lastToken.split(':'); - const dropdownName = split[0].split(' ').last(); + const dropdownName = _.last(split[0].split(' ')); this.loadDropdown(split.length > 1 ? dropdownName : ''); } else if (lastToken) { // Token has been initialized into an object because it has a value diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 7872e9e68ad..a31be2b0bc7 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -20,13 +20,13 @@ class FilteredSearchManager { allowedKeys: this.filteredSearchTokenKeys.getKeys(), }); this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); - const projectPath = this.searchHistoryDropdownElement ? - this.searchHistoryDropdownElement.dataset.projectFullPath : 'project'; + const fullPath = this.searchHistoryDropdownElement ? + this.searchHistoryDropdownElement.dataset.fullPath : 'project'; let recentSearchesPagePrefix = 'issue-recent-searches'; if (this.page === 'merge_requests') { recentSearchesPagePrefix = 'merge-request-recent-searches'; } - const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`; + const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`; this.recentSearchesService = new RecentSearchesService(recentSearchesKey); } @@ -367,7 +367,7 @@ class FilteredSearchManager { const fragments = searchToken.split(':'); if (fragments.length > 1) { const inputValues = fragments[0].split(' '); - const tokenKey = inputValues.last(); + const tokenKey = _.last(inputValues); if (inputValues.length > 1) { inputValues.pop(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index e9278140af0..243ee4d723a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -58,29 +58,54 @@ class FilteredSearchVisualTokens { `; } + static setTokenStyle(tokenContainer, backgroundColor, textColor) { + const token = tokenContainer; + + // Labels with linear gradient should not override default background color + if (backgroundColor.indexOf('linear-gradient') === -1) { + token.style.backgroundColor = backgroundColor; + } + + token.style.color = textColor; + + if (textColor === '#FFFFFF') { + const removeToken = token.querySelector('.remove-token'); + removeToken.classList.add('inverted'); + } + + return token; + } + + static preprocessLabel(labelsEndpoint, labels) { + let processed = labels; + + if (!labels.preprocessed) { + processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels); + AjaxCache.override(labelsEndpoint, processed); + processed.preprocessed = true; + } + + return processed; + } + static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const baseEndpoint = filteredSearchInput.dataset.baseEndpoint; const labelsEndpoint = `${baseEndpoint}/labels.json`; return AjaxCache.retrieve(labelsEndpoint) - .then((labels) => { - const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - - if (!matchingLabel) { - return; - } + .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) + .then((labels) => { + const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - const tokenValueStyle = tokenValueContainer.style; - tokenValueStyle.backgroundColor = matchingLabel.color; - tokenValueStyle.color = matchingLabel.text_color; + if (!matchingLabel) { + return; + } - if (matchingLabel.text_color === '#FFFFFF') { - const removeToken = tokenValueContainer.querySelector('.remove-token'); - removeToken.classList.add('inverted'); - } - }) - .catch(() => new Flash('An error occurred while fetching label colors.')); + FilteredSearchVisualTokens + .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color); + }) + .catch(() => new Flash('An error occurred while fetching label colors.')); } static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js new file mode 100644 index 00000000000..aabea56408a --- /dev/null +++ b/app/assets/javascripts/fly_out_nav.js @@ -0,0 +1,64 @@ +/* global bp */ +import Cookies from 'js-cookie'; +import './breakpoints'; + +export const canShowActiveSubItems = (el) => { + const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md'; + + if (el.classList.contains('active') && !isHiddenByMedia) { + return Cookies.get('sidebar_collapsed') === 'true'; + } + + return true; +}; +export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg'; + +export const calculateTop = (boundingRect, outerHeight) => { + const windowHeight = window.innerHeight; + const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); + + return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height : + boundingRect.top; +}; + +export const showSubLevelItems = (el) => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + + if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return; + + subItems.style.display = 'block'; + el.classList.add('is-showing-fly-out'); + el.classList.add('is-over'); + + const boundingRect = el.getBoundingClientRect(); + const top = calculateTop(boundingRect, subItems.offsetHeight); + const isAbove = top < boundingRect.top; + + subItems.classList.add('fly-out-list'); + subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; + + if (isAbove) { + subItems.classList.add('is-above'); + } +}; + +export const hideSubLevelItems = (el) => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + + if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return; + + el.classList.remove('is-showing-fly-out'); + el.classList.remove('is-over'); + subItems.style.display = 'none'; + subItems.classList.remove('is-above'); +}; + +export default () => { + const items = [...document.querySelectorAll('.sidebar-top-level-items > li')] + .filter(el => el.querySelector('.sidebar-sub-level-items')); + + items.forEach((el) => { + el.addEventListener('mouseenter', e => showSubLevelItems(e.currentTarget)); + el.addEventListener('mouseleave', e => hideSubLevelItems(e.currentTarget)); + }); +}; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 6cb9cfe1382..5c624b79d45 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 3babe273100..b62acfcd445 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,8 +1,53 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ +/* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* global fuzzaldrinPlus */ +import _ from 'underscore'; import { isObject } from './lib/utils/type_utility'; -var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote; +var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; + +GitLabDropdownInput = (function() { + function GitLabDropdownInput(input, options) { + var $inputContainer, $clearButton; + var _this = this; + this.input = input; + this.options = options; + this.fieldName = this.options.fieldName || 'field-name'; + $inputContainer = this.input.parent(); + $clearButton = $inputContainer.find('.js-dropdown-input-clear'); + $clearButton.on('click', (function(_this) { + // Clear click + return function(e) { + e.preventDefault(); + e.stopPropagation(); + return _this.input.val('').trigger('input').focus(); + }; + })(this)); + + this.input + .on('keydown', function (e) { + var keyCode = e.which; + if (keyCode === 13 && !options.elIsInput) { + e.preventDefault(); + } + }) + .on('input', function(e) { + var val = e.currentTarget.value || _this.options.inputFieldName; + val = val.split(' ').join('-') // replaces space with dash + .replace(/[^a-zA-Z0-9 -]/g, '').toLowerCase() // replace non alphanumeric + .replace(/(-)\1+/g, '-'); // replace repeated dashes + _this.cb(_this.options.fieldName, val, {}, true); + _this.input.closest('.dropdown') + .find('.dropdown-toggle-text') + .text(val); + }); + } + + GitLabDropdownInput.prototype.onInput = function(cb) { + this.cb = cb; + }; + + return GitLabDropdownInput; +})(); GitLabDropdownFilter = (function() { var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS; @@ -114,7 +159,7 @@ GitLabDropdownFilter = (function() { } else { elements = this.options.elements(); if (search_text) { - return elements.each(function() { + elements.each(function() { var $el, matches; $el = $(this); matches = fuzzaldrinPlus.match($el.text().trim(), search_text); @@ -127,8 +172,10 @@ GitLabDropdownFilter = (function() { } }); } else { - return elements.show().removeClass('option-hidden'); + elements.show().removeClass('option-hidden'); } + + elements.parent().find('.dropdown-menu-empty-link').toggleClass('hidden', elements.is(':visible')); } }; @@ -188,7 +235,7 @@ GitLabDropdownRemote = (function() { })(); GitLabDropdown = (function() { - var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex; + var ACTIVE_CLASS, FILTER_INPUT, NO_FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex; LOADING_CLASS = "is-loading"; @@ -206,7 +253,9 @@ GitLabDropdown = (function() { CURSOR_SELECT_SCROLL_PADDING = 5; - FILTER_INPUT = '.dropdown-input .dropdown-input-field'; + FILTER_INPUT = '.dropdown-input .dropdown-input-field:not(.dropdown-no-filter)'; + + NO_FILTER_INPUT = '.dropdown-input .dropdown-input-field.dropdown-no-filter'; function GitLabDropdown(el1, options) { var searchFields, selector, self; @@ -221,6 +270,7 @@ GitLabDropdown = (function() { this.dropdown = selector != null ? $(selector) : $(this.el).parent(); // Set Defaults this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT); + this.noFilterInput = this.options.noFilterInput || this.getElement(NO_FILTER_INPUT); this.highlight = !!this.options.highlight; this.filterInputBlur = this.options.filterInputBlur != null ? this.options.filterInputBlur @@ -259,6 +309,10 @@ GitLabDropdown = (function() { }); } } + if (this.noFilterInput.length) { + this.plainInput = new GitLabDropdownInput(this.noFilterInput, this.options); + this.plainInput.onInput(this.addInput.bind(this)); + } // Init filterable if (this.options.filterable) { this.filter = new GitLabDropdownFilter(this.filterInput, { @@ -730,10 +784,16 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { if (this.options.filterable) { - $(':focus').blur(); - this.dropdown.one('transitionend', () => { - this.filterInput.focus(); + const initialScrollTop = $(window).scrollTop(); + + if (this.dropdown.is('.open')) { + this.filterInput.focus(); + } + + if ($(window).scrollTop() < initialScrollTop) { + $(window).scrollTop(initialScrollTop); + } }); if (triggerFocus) { @@ -744,9 +804,13 @@ GitLabDropdown = (function() { } }; - GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { + GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject, single) { var $input; // Create hidden input for form + if (single) { + $('input[name="' + fieldName + '"]').remove(); + } + $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value); if (this.options.inputId != null) { $input.attr('id', this.options.inputId); @@ -762,7 +826,7 @@ GitLabDropdown = (function() { $input.attr('data-meta', selectedObject[this.options.inputMeta]); } - return this.dropdown.before($input); + this.dropdown.before($input).trigger('change'); }; GitLabDropdown.prototype.selectRowAtIndex = function(index) { diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index a433c7ba8f0..534bc535bb6 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,6 +1,4 @@ import Chart from 'vendor/Chart'; -import ContributorsStatGraph from './stat_graph_contributors'; // export to global scope window.Chart = Chart; -window.ContributorsStatGraph = ContributorsStatGraph; diff --git a/app/assets/javascripts/graphs/graphs_charts.js b/app/assets/javascripts/graphs/graphs_charts.js new file mode 100644 index 00000000000..ec6eab34989 --- /dev/null +++ b/app/assets/javascripts/graphs/graphs_charts.js @@ -0,0 +1,61 @@ +import Chart from 'vendor/Chart'; +import _ from 'underscore'; + +document.addEventListener('DOMContentLoaded', () => { + const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML); + + const responsiveChart = (selector, data) => { + const options = { + scaleOverlay: true, + responsive: true, + pointHitDetectionRadius: 2, + maintainAspectRatio: false, + }; + // get selector by context + const ctx = selector.get(0).getContext('2d'); + // pointing parent container to make chart.js inherit its width + const container = $(selector).parent(); + const generateChart = () => { + selector.attr('width', $(container).width()); + if (window.innerWidth < 768) { + // Scale fonts if window width lower than 768px (iPad portrait) + options.scaleFontSize = 8; + } + return new Chart(ctx).Bar(data, options); + }; + // enabling auto-resizing + $(window).resize(generateChart); + return generateChart(); + }; + + const chartData = data => ({ + labels: Object.keys(data), + datasets: [{ + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: _.values(data), + }], + }); + + const hourData = chartData(projectChartData.hour); + responsiveChart($('#hour-chart'), hourData); + + const dayData = chartData(projectChartData.weekDays); + responsiveChart($('#weekday-chart'), dayData); + + const monthData = chartData(projectChartData.month); + responsiveChart($('#month-chart'), monthData); + + const data = projectChartData.languages; + const ctx = $('#languages-chart').get(0).getContext('2d'); + const options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false, + }; + + new Chart(ctx).Pie(data, options); +}); diff --git a/app/assets/javascripts/graphs/graphs_show.js b/app/assets/javascripts/graphs/graphs_show.js new file mode 100644 index 00000000000..36bad6db3e1 --- /dev/null +++ b/app/assets/javascripts/graphs/graphs_show.js @@ -0,0 +1,21 @@ +import ContributorsStatGraph from './stat_graph_contributors'; + +document.addEventListener('DOMContentLoaded', () => { + $.ajax({ + type: 'GET', + url: document.querySelector('.js-graphs-show').dataset.projectGraphPath, + dataType: 'json', + success(data) { + const graph = new ContributorsStatGraph(); + graph.init(data); + + $('#brush_change').change(() => { + graph.change_date_header(); + graph.redraw_authors(); + }); + + $('.stat-graph').fadeIn(); + $('.loading-graph').hide(); + }, + }); +}); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index c6be4c9e8fe..cdc4fcf6573 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */ +import _ from 'underscore'; import d3 from 'd3'; import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph'; import ContributorsStatGraphUtil from './stat_graph_contributors_util'; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 0deb27e522b..f64b4638485 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -1,5 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ - +import _ from 'underscore'; import d3 from 'd3'; const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index c583757f3f2..77135ad1f0e 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */ +import _ from 'underscore'; export default { parse_log: function(log) { diff --git a/app/assets/javascripts/groups/components/group_identicon.vue b/app/assets/javascripts/groups/components/group_identicon.vue new file mode 100644 index 00000000000..0edd820743f --- /dev/null +++ b/app/assets/javascripts/groups/components/group_identicon.vue @@ -0,0 +1,45 @@ +<script> +export default { + props: { + entityId: { + type: Number, + required: true, + }, + entityName: { + type: String, + required: true, + }, + }, + computed: { + /** + * This method is based on app/helpers/application_helper.rb#project_identicon + */ + identiconStyles() { + const allowedColors = [ + '#FFEBEE', + '#F3E5F5', + '#E8EAF6', + '#E3F2FD', + '#E0F2F1', + '#FBE9E7', + '#EEEEEE', + ]; + + const backgroundColor = allowedColors[this.entityId % 7]; + + return `background-color: ${backgroundColor}; color: #555;`; + }, + identiconTitle() { + return this.entityName.charAt(0).toUpperCase(); + }, + }, +}; +</script> + +<template> + <div + class="avatar s40 identicon" + :style="identiconStyles"> + {{identiconTitle}} + </div> +</template> diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index b1db34b9c50..cb133cf7535 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -1,7 +1,11 @@ <script> import eventHub from '../event_hub'; +import groupIdenticon from './group_identicon.vue'; export default { + components: { + groupIdenticon, + }, props: { group: { type: Object, @@ -92,6 +96,9 @@ export default { hasGroups() { return Object.keys(this.group.subGroups).length > 0; }, + hasAvatar() { + return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1; + }, }, }; </script> @@ -194,9 +201,15 @@ export default { <a :href="group.groupPath"> <img + v-if="hasAvatar" class="avatar s40" :src="group.avatarUrl" /> + <group-identicon + v-else + :entity-id=group.id + :entity-name="group.name" + /> </a> </div> <div diff --git a/app/assets/javascripts/helpers/user_feature_helper.js b/app/assets/javascripts/helpers/user_feature_helper.js new file mode 100644 index 00000000000..fcd8569819c --- /dev/null +++ b/app/assets/javascripts/helpers/user_feature_helper.js @@ -0,0 +1,11 @@ +import Cookies from 'js-cookie'; + +function isNewRepo() { + return Cookies.get('new_repo') === 'true'; +} + +const UserFeatureHelper = { + isNewRepo, +}; + +export default UserFeatureHelper; diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index e46c0e90255..c39ffdb2e0f 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ /* global IssuableIndex */ /* global Flash */ +import _ from 'underscore'; export default { init({ container, form, issues, prefixId } = {}) { diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 9ac1325fc95..3f848e0859b 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -2,8 +2,8 @@ /* global GitLab */ /* global Autosave */ /* global dateFormat */ -/* global Pikaday */ +import Pikaday from 'pikaday'; import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index 5c96646def8..ece0220c927 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ /* global IssuableIndex */ - +import _ from 'underscore'; import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 8d7d3d73571..7d7f91227f9 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,8 +1,9 @@ /* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */ /* global Issuable */ /* global ListLabel */ - +import _ from 'underscore'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; +import DropdownUtils from './filtered_search/dropdown_utils'; (function() { this.LabelsSelect = (function() { @@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; } } if (label.duplicate) { - spacing = 100 / label.color.length; - // Reduce the colors to 4 - label.color = label.color.filter(function(color, i) { - return i < 4; - }); - color = _.map(label.color, function(color, i) { - var percentFirst, percentSecond; - percentFirst = Math.floor(spacing * i); - percentSecond = Math.floor(spacing * (i + 1)); - return color + " " + percentFirst + "%," + color + " " + percentSecond + "% "; - }).join(','); - color = "linear-gradient(" + color + ")"; + color = gl.DropdownUtils.duplicateLabelColor(label.color); } else { if (label.color != null) { diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 6186ffe20b3..5c1ba416a03 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import Cookies from 'js-cookie'; import NewNavSidebar from './new_sidebar'; +import initFlyOutNav from './fly_out_nav'; (function() { var hideEndFade; @@ -58,6 +59,8 @@ import NewNavSidebar from './new_sidebar'; if (Cookies.get('new_nav') === 'true') { const newNavSidebar = new NewNavSidebar(); newNavSidebar.bindEvents(); + + initFlyOutNav(); } $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js index 7477b5a5214..629d8f44e18 100644 --- a/app/assets/javascripts/lib/utils/ajax_cache.js +++ b/app/assets/javascripts/lib/utils/ajax_cache.js @@ -6,6 +6,10 @@ class AjaxCache extends Cache { this.pendingRequests = { }; } + override(endpoint, data) { + this.internalStorage[endpoint] = data; + } + retrieve(endpoint, forceRetrieve) { if (this.hasData(endpoint) && !forceRetrieve) { return Promise.resolve(this.get(endpoint)); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 122ec138c59..e916724b666 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -86,8 +86,9 @@ // This is required to handle non-unicode characters in hash hash = decodeURIComponent(hash); - var fixedTabs = document.querySelector('.js-tabs-affix'); - var fixedNav = document.querySelector('.navbar-gitlab'); + const fixedTabs = document.querySelector('.js-tabs-affix'); + const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck'); + const fixedNav = document.querySelector('.navbar-gitlab'); var adjustment = 0; if (fixedNav) adjustment -= fixedNav.offsetHeight; @@ -104,6 +105,11 @@ if (fixedTabs) { adjustment -= fixedTabs.offsetHeight; } + + if (fixedDiffStats) { + adjustment -= fixedDiffStats.offsetHeight; + } + window.scrollBy(0, adjustment); } }; diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js index 1e96c7ab5cd..7a72509d234 100644 --- a/app/assets/javascripts/lib/utils/constants.js +++ b/app/assets/javascripts/lib/utils/constants.js @@ -1,2 +1,3 @@ /* eslint-disable import/prefer-default-export */ export const BYTES_IN_KIB = 1024; +export const HIDDEN_CLASS = 'hidden'; diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js index ae397212e55..716aefbfcb7 100644 --- a/app/assets/javascripts/lib/utils/pretty_time.js +++ b/app/assets/javascripts/lib/utils/pretty_time.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + (() => { /* * TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints, diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js new file mode 100644 index 00000000000..43a808b6ab3 --- /dev/null +++ b/app/assets/javascripts/lib/utils/sticky.js @@ -0,0 +1,23 @@ +export const isSticky = (el, scrollY, stickyTop) => { + const top = el.offsetTop - scrollY; + + if (top === stickyTop) { + el.classList.add('is-stuck'); + } else { + el.classList.remove('is-stuck'); + } +}; + +export default (el) => { + if (!el) return; + + const computedStyle = window.getComputedStyle(el); + + if (!/sticky/.test(computedStyle.position)) return; + + const stickyTop = parseInt(computedStyle.top, 10); + + document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop), { + passive: true, + }); +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index cd45091c211..e0c61a474c6 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -7,7 +7,6 @@ import jQuery from 'jquery'; import _ from 'underscore'; import Cookies from 'js-cookie'; -import Pikaday from 'pikaday'; import Dropzone from 'dropzone'; import Sortable from 'vendor/Sortable'; @@ -16,14 +15,10 @@ import 'mousetrap'; import 'mousetrap/plugins/pause/mousetrap-pause'; import 'vendor/fuzzaldrin-plus'; -// extensions -import './extensions/array'; - // expose common libraries as globals (TODO: remove these) window.jQuery = jQuery; window.$ = jQuery; window._ = _; -window.Pikaday = Pikaday; window.Dropzone = Dropzone; window.Sortable = Sortable; @@ -36,9 +31,6 @@ import './shortcuts_find_file'; import './shortcuts_issuable'; import './shortcuts_network'; -// behaviors -import './behaviors/'; - // templates import './templates/issuable_template_selector'; import './templates/issuable_template_selectors'; @@ -56,6 +48,9 @@ import './lib/utils/pretty_time'; import './lib/utils/text_utility'; import './lib/utils/url_utility'; +// behaviors +import './behaviors/'; + // u2f import './u2f/authenticate'; import './u2f/error'; @@ -86,7 +81,6 @@ import './copy_as_gfm'; import './copy_to_clipboard'; import './create_label'; import './diff'; -import './dispatcher'; import './dropzone_input'; import './due_date_select'; import './files_comment_button'; @@ -150,9 +144,13 @@ import './subscription'; import './subscription_select'; import './syntax_highlight'; +import './dispatcher'; + // eslint-disable-next-line global-require, import/no-commonjs if (process.env.NODE_ENV !== 'production') require('./test_utils/'); +Dropzone.autoDiscover = false; + document.addEventListener('beforeunload', function () { // Unbind scroll events $(document).off('scroll'); diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index e034729bd39..cc9016e74da 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -1,5 +1,7 @@ -/* global Pikaday */ /* global dateFormat */ + +import Pikaday from 'pikaday'; + (() => { // Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 7840f05a8ae..4ffd71d9de5 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -7,6 +7,7 @@ import Cookies from 'js-cookie'; import './breakpoints'; import './flash'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; +import stickyMonitor from './lib/utils/sticky'; /* eslint-disable max-len */ // MergeRequestTabs @@ -266,6 +267,10 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; const $container = $('#diffs'); $container.html(data.html); + this.initChangesDropdown(); + + stickyMonitor(document.querySelector('.js-diff-files-changed')); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { gl.diffNotesCompileComponents(); } @@ -314,6 +319,13 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; }); } + initChangesDropdown() { + $('.js-diff-stats-dropdown').glDropdown({ + filterable: true, + remoteFilter: false, + }); + } + // Show or hide the loading spinner // // status - Boolean, true to show, false to hide diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 6756ab0b3aa..04579058688 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ /* global ListMilestone */ +import _ from 'underscore'; (function() { this.MilestoneSelect = (function() { diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index 5f98aff8ced..930218dd1f5 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -1,23 +1,65 @@ +import Cookies from 'js-cookie'; +import _ from 'underscore'; +/* global bp */ +import './breakpoints'; + export default class NewNavSidebar { constructor() { this.initDomElements(); + this.render(); } initDomElements() { + this.$page = $('.page-with-sidebar'); this.$sidebar = $('.nav-sidebar'); this.$overlay = $('.mobile-overlay'); this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); + this.$sidebarToggle = $('.js-toggle-sidebar'); } bindEvents() { this.$openSidebar.on('click', () => this.toggleSidebarNav(true)); this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); this.$overlay.on('click', () => this.toggleSidebarNav(false)); + this.$sidebarToggle.on('click', () => { + const value = !this.$sidebar.hasClass('sidebar-icons-only'); + this.toggleCollapsedSidebar(value); + }); + + $(window).on('resize', () => _.debounce(this.render(), 100)); + } + + static setCollapsedCookie(value) { + if (bp.getBreakpointSize() !== 'lg') { + return; + } + Cookies.set('sidebar_collapsed', value, { expires: 365 * 10 }); } toggleSidebarNav(show) { this.$sidebar.toggleClass('nav-sidebar-expanded', show); this.$overlay.toggleClass('mobile-nav-open', show); + this.$sidebar.removeClass('sidebar-icons-only'); + } + + toggleCollapsedSidebar(collapsed) { + this.$sidebar.toggleClass('sidebar-icons-only', collapsed); + if (this.$sidebar.length) { + this.$page.toggleClass('page-with-new-sidebar', !collapsed); + this.$page.toggleClass('page-with-icon-sidebar', collapsed); + } + NewNavSidebar.setCollapsedCookie(collapsed); + } + + render() { + const breakpoint = bp.getBreakpointSize(); + + if (breakpoint === 'sm' || breakpoint === 'md') { + this.toggleCollapsedSidebar(true); + } else if (breakpoint === 'lg') { + const collapse = Cookies.get('sidebar_collapsed') === 'true'; + this.toggleCollapsedSidebar(collapse); + } } } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index dfa07a2def4..b38a6abc8d1 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -11,6 +11,7 @@ newline-per-chained-call, no-useless-escape, class-methods-use-this */ /* global mrRefreshWidgetUrl */ import $ from 'jquery'; +import _ from 'underscore'; import Cookies from 'js-cookie'; import autosize from 'vendor/autosize'; import Dropzone from 'dropzone'; diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue index 4603859d7b0..b874e484d45 100644 --- a/app/assets/javascripts/pdf/index.vue +++ b/app/assets/javascripts/pdf/index.vue @@ -9,8 +9,8 @@ </template> <script> - import pdfjsLib from 'pdfjs-dist'; - import workerSrc from 'vendor/pdf.worker'; + import pdfjsLib from 'vendor/pdf'; + import workerSrc from 'vendor/pdf.worker.min'; import page from './page/index.vue'; diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue index ce46b3fa3fa..b5d85299cf8 100644 --- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue @@ -1,4 +1,6 @@ <script> + import _ from 'underscore'; + export default { props: { initialCronInterval: { diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 77cbaeb43ef..66bc1d1979c 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,7 +1,7 @@ <script> + import loadingIcon from '~/vue_shared/components/loading_icon.vue'; + import '~/flash'; import stageColumnComponent from './stage_column_component.vue'; - import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; - import '../../../flash'; export default { props: { diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index cf1566eeb87..291ae24aa68 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -1,6 +1,7 @@ /* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */ -import 'vendor/cropper'; +import 'cropper'; +import _ from 'underscore'; ((global) => { // Matches everything but the file name diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index a3f7d69b98d..1c2100a1c25 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -10,14 +10,19 @@ import Cookies from 'js-cookie'; const $projectCloneField = $('#project_clone'); const $cloneBtnText = $('a.clone-dropdown-btn span'); + const selectedCloneOption = $cloneBtnText.text().trim(); + if (selectedCloneOption.length > 0) { + $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); + } + $('a', $cloneOptions).on('click', (e) => { const $this = $(e.currentTarget); const url = $this.attr('href'); e.preventDefault(); - $('.active', $cloneOptions).not($this).removeClass('active'); - $this.toggleClass('active'); + $('.is-active', $cloneOptions).not($this).removeClass('is-active'); + $this.toggleClass('is-active'); $projectCloneField.val(url); $cloneBtnText.text($this.text()); @@ -85,6 +90,7 @@ import Cookies from 'js-cookie'; filterable: true, filterRemote: true, filterByText: true, + inputFieldName: $dropdown.data('input-field-name'), fieldName: $dropdown.data('field-name'), renderRow: function(ref) { var li = refListItem.cloneNode(false); @@ -118,9 +124,14 @@ import Cookies from 'js-cookie'; e.preventDefault(); if ($('input[name="ref"]').length) { var $form = $dropdown.closest('form'); + + var $visit = $dropdown.data('visit'); + var shouldVisit = typeof $visit === 'undefined' ? true : $visit; var action = $form.attr('action'); var divider = action.indexOf('?') === -1 ? '?' : '&'; - gl.utils.visitUrl(action + '' + divider + '' + $form.serialize()); + if (shouldVisit) { + gl.utils.visitUrl(action + '' + divider + '' + $form.serialize()); + } } } }); diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js index d7d284b6c86..7572fec15e0 100644 --- a/app/assets/javascripts/project_edit.js +++ b/app/assets/javascripts/project_edit.js @@ -1,6 +1,6 @@ export default function setupProjectEdit() { const $transferForm = $('.js-project-transfer-form'); - const $selectNamespace = $transferForm.find('.select2'); + const $selectNamespace = $transferForm.find('select.select2'); $selectNamespace.on('change', () => { $transferForm.find(':submit').prop('disabled', !$selectNamespace.val()); diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index ebcefc819f5..1b4ed6be90a 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ import Api from './api'; +import ProjectSelectComboButton from './project_select_combo_button'; (function() { this.ProjectSelect = (function() { @@ -58,7 +59,8 @@ import Api from './api'; if (this.includeGroups) { placeholder += " or group"; } - return $(select).select2({ + + $(select).select2({ placeholder: placeholder, minimumInputLength: 0, query: (function(_this) { @@ -96,21 +98,18 @@ import Api from './api'; }; })(this), id: function(project) { - return project.web_url; + return JSON.stringify({ + name: project.name, + url: project.web_url, + }); }, text: function(project) { return project.name_with_namespace || project.name; }, dropdownCssClass: "ajax-project-dropdown" }); - }); - - $('.new-project-item-select-button').on('click', function() { - $('.project-item-select', this.parentNode).select2('open'); - }); - $('.project-item-select').on('click', function() { - window.location = `${$(this).val()}/${this.dataset.relativePath}`; + return new ProjectSelectComboButton(select); }); } diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js new file mode 100644 index 00000000000..f799d9d619a --- /dev/null +++ b/app/assets/javascripts/project_select_combo_button.js @@ -0,0 +1,85 @@ +import AccessorUtilities from './lib/utils/accessor'; + +export default class ProjectSelectComboButton { + constructor(select) { + this.projectSelectInput = $(select); + this.newItemBtn = $('.new-project-item-link'); + this.newItemBtnBaseText = this.newItemBtn.data('label'); + this.itemType = this.deriveItemTypeFromLabel(); + this.groupId = this.projectSelectInput.data('groupId'); + + this.bindEvents(); + this.initLocalStorage(); + } + + bindEvents() { + this.projectSelectInput.siblings('.new-project-item-select-button') + .on('click', this.openDropdown); + + this.projectSelectInput.on('change', () => this.selectProject()); + } + + initLocalStorage() { + const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); + + if (localStorageIsSafe) { + const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-'); + + this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-'); + this.setBtnTextFromLocalStorage(); + } + } + + openDropdown() { + $(this).siblings('.project-item-select').select2('open'); + } + + selectProject() { + const selectedProjectData = JSON.parse(this.projectSelectInput.val()); + const projectUrl = `${selectedProjectData.url}/${this.projectSelectInput.data('relativePath')}`; + const projectName = selectedProjectData.name; + + const projectMeta = { + url: projectUrl, + name: projectName, + }; + + this.setNewItemBtnAttributes(projectMeta); + this.setProjectInLocalStorage(projectMeta); + } + + setBtnTextFromLocalStorage() { + const cachedProjectData = this.getProjectFromLocalStorage(); + + this.setNewItemBtnAttributes(cachedProjectData); + } + + setNewItemBtnAttributes(project) { + if (project) { + this.newItemBtn.attr('href', project.url); + this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`); + this.newItemBtn.enable(); + } else { + this.newItemBtn.text(`Select project to create ${this.itemType}`); + this.newItemBtn.disable(); + } + } + + deriveItemTypeFromLabel() { + // label is either 'New issue' or 'New merge request' + return this.newItemBtnBaseText.split(' ').slice(1).join(' '); + } + + getProjectFromLocalStorage() { + const projectString = localStorage.getItem(this.localStorageKey); + + return JSON.parse(projectString); + } + + setProjectInLocalStorage(projectMeta) { + const projectString = JSON.stringify(projectMeta); + + localStorage.setItem(this.localStorageKey, projectString); + } +} + diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js new file mode 100644 index 00000000000..c34927499fc --- /dev/null +++ b/app/assets/javascripts/projects/project_import_gitlab_project.js @@ -0,0 +1,14 @@ +import '../lib/utils/url_utility'; + +const bindEvents = () => { + const path = gl.utils.getParameterValues('path')[0]; + + // get the path url and append it in the inputS + $('.js-path-name').val(path); +}; + +document.addEventListener('DOMContentLoaded', bindEvents); + +export default { + bindEvents, +}; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 2091b275c3d..985521aef34 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,6 +1,38 @@ -document.addEventListener('DOMContentLoaded', () => { - const importBtnTooltip = 'Please enter a valid project name.'; - const $importBtnWrapper = $('.import_gitlab_project'); +let hasUserDefinedProjectPath = false; + +const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { + if (hasUserDefinedProjectPath) { + return; + } + + let importUrl = $projectImportUrl.val().trim(); + if (importUrl.length === 0) { + return; + } + + /* + \/?: remove trailing slash + (\.git\/?)?: remove trailing .git (with optional trailing slash) + (\?.*)?: remove query string + (#.*)?: remove fragment identifier + */ + importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, ''); + + // extract everything after the last slash + const pathMatch = /\/([^/]+)$/.exec(importUrl); + if (pathMatch) { + $projectPath.val(pathMatch[1]); + } +}; + +const bindEvents = () => { + const $newProjectForm = $('#new_project'); + const $projectImportUrl = $('#project_import_url'); + const $projectPath = $('#project_path'); + + if ($newProjectForm.length !== 1) { + return; + } $('.how_to_import_link').on('click', (e) => { e.preventDefault(); @@ -13,31 +45,23 @@ document.addEventListener('DOMContentLoaded', () => { $('.btn_import_gitlab_project').on('click', () => { const importHref = $('a.btn_import_gitlab_project').attr('href'); - $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$('#project_path').val()}`); + $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`); }); - $('.btn_import_gitlab_project').attr('disabled', !$('#project_path').val().trim().length); - $importBtnWrapper.attr('title', importBtnTooltip); - - $('#new_project').on('submit', () => { - const $path = $('#project_path'); - $path.val($path.val().trim()); + $newProjectForm.on('submit', () => { + $projectPath.val($projectPath.val().trim()); }); - $('#project_path').on('keyup', () => { - if ($('#project_path').val().trim().length) { - $('.btn_import_gitlab_project').attr('disabled', false); - $importBtnWrapper.attr('title', ''); - $importBtnWrapper.removeClass('has-tooltip'); - } else { - $('.btn_import_gitlab_project').attr('disabled', true); - $importBtnWrapper.addClass('has-tooltip'); - } + $projectPath.on('keyup', () => { + hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; }); - $('#project_import_url').disable(); - $('.import_git').on('click', () => { - const $projectImportUrl = $('#project_import_url'); - $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled')); - }); -}); + $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); +}; + +document.addEventListener('DOMContentLoaded', bindEvents); + +export default { + bindEvents, + deriveProjectPathFromUrl, +}; diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js index cc0b2ebe071..678882a8d2c 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default class ProtectedBranchDropdown { /** * @param {Object} options containing diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js index 9d045886262..a0224213aa0 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js +++ b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default class ProtectedTagDropdown { /** * @param {Object} options containing diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue new file mode 100644 index 00000000000..703da749ad3 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo.vue @@ -0,0 +1,63 @@ +<script> +import RepoSidebar from './repo_sidebar.vue'; +import RepoCommitSection from './repo_commit_section.vue'; +import RepoTabs from './repo_tabs.vue'; +import RepoFileButtons from './repo_file_buttons.vue'; +import RepoPreview from './repo_preview.vue'; +import RepoMixin from '../mixins/repo_mixin'; +import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; +import Store from '../stores/repo_store'; +import Helper from '../helpers/repo_helper'; +import MonacoLoaderHelper from '../helpers/monaco_loader_helper'; + +export default { + data: () => Store, + mixins: [RepoMixin], + components: { + 'repo-sidebar': RepoSidebar, + 'repo-tabs': RepoTabs, + 'repo-file-buttons': RepoFileButtons, + 'repo-editor': MonacoLoaderHelper.repoEditorLoader, + 'repo-commit-section': RepoCommitSection, + 'popup-dialog': PopupDialog, + 'repo-preview': RepoPreview, + }, + + mounted() { + Helper.getContent().catch(Helper.loadingError); + }, + + methods: { + dialogToggled(toggle) { + this.dialog.open = toggle; + }, + + dialogSubmitted(status) { + this.dialog.open = false; + this.dialog.status = status; + }, + + toggleBlobView: Store.toggleBlobView, + }, +}; +</script> + +<template> +<div class="repository-view tree-content-holder"> + <repo-sidebar/><div class="panel-right" :class="{'edit-mode': editMode}"> + <repo-tabs/> + <component :is="currentBlobView" class="blob-viewer-container"></component> + <repo-file-buttons/> + </div> + <repo-commit-section/> + <popup-dialog + :primary-button-label="__('Discard changes')" + :open="dialog.open" + kind="warning" + :title="__('Are you sure?')" + :body="__('Are you sure you want to discard your changes?')" + @toggle="dialogToggled" + @submit="dialogSubmitted" + /> +</div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue new file mode 100644 index 00000000000..bd83f80c928 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_commit_section.vue @@ -0,0 +1,100 @@ +<script> +/* global Flash */ +import Store from '../stores/repo_store'; +import RepoMixin from '../mixins/repo_mixin'; +import Helper from '../helpers/repo_helper'; +import Service from '../services/repo_service'; + +const RepoCommitSection = { + data: () => Store, + + mixins: [RepoMixin], + + computed: { + branchPaths() { + const branch = Helper.getBranch(); + return this.changedFiles.map(f => Helper.getFilePathFromFullPath(f.url, branch)); + }, + + cantCommitYet() { + return !this.commitMessage || this.submitCommitsLoading; + }, + + filePluralize() { + return this.changedFiles.length > 1 ? 'files' : 'file'; + }, + }, + + methods: { + makeCommit() { + // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions + const branch = Helper.getBranch(); + const commitMessage = this.commitMessage; + const actions = this.changedFiles.map(f => ({ + action: 'update', + file_path: Helper.getFilePathFromFullPath(f.url, branch), + content: f.newContent, + })); + const payload = { + branch: Store.targetBranch, + commit_message: commitMessage, + actions, + }; + Store.submitCommitsLoading = true; + Service.commitFiles(payload, this.resetCommitState); + }, + + resetCommitState() { + this.submitCommitsLoading = false; + this.changedFiles = []; + this.openedFiles = []; + this.commitMessage = ''; + this.editMode = false; + $('html, body').animate({ scrollTop: 0 }, 'fast'); + }, + }, +}; + +export default RepoCommitSection; +</script> + +<template> +<div id="commit-area" v-if="isCommitable && changedFiles.length" > + <form class="form-horizontal"> + <fieldset> + <div class="form-group"> + <label class="col-md-4 control-label staged-files">Staged files ({{changedFiles.length}})</label> + <div class="col-md-4"> + <ul class="list-unstyled changed-files"> + <li v-for="file in branchPaths" :key="file.id"> + <span class="help-block">{{file}}</span> + </li> + </ul> + </div> + </div> + <!-- Textarea + --> + <div class="form-group"> + <label class="col-md-4 control-label" for="commit-message">Commit message</label> + <div class="col-md-4"> + <textarea class="form-control" id="commit-message" name="commit-message" v-model="commitMessage"></textarea> + </div> + </div> + <!-- Button Drop Down + --> + <div class="form-group target-branch"> + <label class="col-md-4 control-label" for="target-branch">Target branch</label> + <div class="col-md-4"> + <span class="help-block">{{targetBranch}}</span> + </div> + </div> + <div class="col-md-offset-4 col-md-4"> + <button type="submit" :disabled="cantCommitYet" class="btn btn-success submit-commit" @click.prevent="makeCommit"> + <i class="fa fa-spinner fa-spin" v-if="submitCommitsLoading"></i> + <span class="commit-summary">Commit {{changedFiles.length}} {{filePluralize}}</span> + </button> + </div> + </fieldset> + </form> +</div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue new file mode 100644 index 00000000000..e954fd38fc9 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_edit_button.vue @@ -0,0 +1,49 @@ +<script> +import Store from '../stores/repo_store'; +import RepoMixin from '../mixins/repo_mixin'; + +export default { + data: () => Store, + mixins: [RepoMixin], + computed: { + buttonLabel() { + return this.editMode ? this.__('Cancel edit') : this.__('Edit'); + }, + + buttonIcon() { + return this.editMode ? [] : ['fa', 'fa-pencil']; + }, + }, + methods: { + editClicked() { + if (this.changedFiles.length) { + this.dialog.open = true; + return; + } + this.editMode = !this.editMode; + Store.toggleBlobView(); + }, + }, + + watch: { + editMode() { + if (this.editMode) { + $('.project-refs-form').addClass('disabled'); + $('.fa-long-arrow-right').show(); + $('.project-refs-target-form').show(); + } else { + $('.project-refs-form').removeClass('disabled'); + $('.fa-long-arrow-right').hide(); + $('.project-refs-target-form').hide(); + } + }, + }, +}; +</script> + +<template> +<button class="btn btn-default" @click.prevent="editClicked" v-cloak v-if="isCommitable && !activeFile.render_error" :disabled="binary"> + <i :class="buttonIcon"></i> + <span>{{buttonLabel}}</span> +</button> +</template> diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue new file mode 100644 index 00000000000..fd1a21e15b4 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_editor.vue @@ -0,0 +1,135 @@ +<script> +/* global monaco */ +import Store from '../stores/repo_store'; +import Service from '../services/repo_service'; +import Helper from '../helpers/repo_helper'; + +const RepoEditor = { + data: () => Store, + + destroyed() { + // this.monacoInstance.getModels().forEach((m) => { + // m.dispose(); + // }); + this.monacoInstance.destroy(); + }, + + mounted() { + Service.getRaw(this.activeFile.raw_path) + .then((rawResponse) => { + Store.blobRaw = rawResponse.data; + Helper.findOpenedFileFromActive().plain = rawResponse.data; + + const monacoInstance = this.monaco.editor.create(this.$el, { + model: null, + readOnly: false, + contextmenu: false, + }); + + Store.monacoInstance = monacoInstance; + + this.addMonacoEvents(); + + const languages = this.monaco.languages.getLanguages(); + const languageID = Helper.getLanguageIDForFile(this.activeFile, languages); + this.showHide(); + const newModel = this.monaco.editor.createModel(this.blobRaw, languageID); + + this.monacoInstance.setModel(newModel); + }).catch(Helper.loadingError); + }, + + methods: { + showHide() { + if (!this.openedFiles.length || (this.binary && !this.activeFile.raw)) { + this.$el.style.display = 'none'; + } else { + this.$el.style.display = 'inline-block'; + } + }, + + addMonacoEvents() { + this.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp); + this.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this)); + }, + + onMonacoEditorKeysPressed() { + Store.setActiveFileContents(this.monacoInstance.getValue()); + }, + + onMonacoEditorMouseUp(e) { + const lineNumber = e.target.position.lineNumber; + if (e.target.element.className === 'line-numbers') { + location.hash = `L${lineNumber}`; + Store.activeLine = lineNumber; + } + }, + }, + + watch: { + activeLine() { + this.monacoInstance.setPosition({ + lineNumber: this.activeLine, + column: 1, + }); + }, + + activeFileLabel() { + this.showHide(); + }, + + dialog: { + handler(obj) { + const newObj = obj; + if (newObj.status) { + newObj.status = false; + this.openedFiles.map((file) => { + const f = file; + if (f.active) { + this.blobRaw = f.plain; + } + f.changed = false; + delete f.newContent; + + return f; + }); + this.editMode = false; + } + }, + deep: true, + }, + + isTree() { + this.showHide(); + }, + + openedFiles() { + this.showHide(); + }, + + binary() { + this.showHide(); + }, + + blobRaw() { + this.showHide(); + + if (this.isTree) return; + + this.monacoInstance.setModel(null); + + const languages = this.monaco.languages.getLanguages(); + const languageID = Helper.getLanguageIDForFile(this.activeFile, languages); + const newModel = this.monaco.editor.createModel(this.blobRaw, languageID); + + this.monacoInstance.setModel(newModel); + }, + }, +}; + +export default RepoEditor; +</script> + +<template> +<div id="ide"></div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue new file mode 100644 index 00000000000..f604bc22a26 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_file.vue @@ -0,0 +1,66 @@ +<script> +import TimeAgoMixin from '../../vue_shared/mixins/timeago'; + +const RepoFile = { + mixins: [TimeAgoMixin], + props: { + file: { + type: Object, + required: true, + }, + isMini: { + type: Boolean, + required: false, + default: false, + }, + loading: { + type: Object, + required: false, + default() { return { tree: false }; }, + }, + hasFiles: { + type: Boolean, + required: false, + default: false, + }, + activeFile: { + type: Object, + required: true, + }, + }, + + computed: { + canShowFile() { + return !this.loading.tree || this.hasFiles; + }, + }, + + methods: { + linkClicked(file) { + this.$emit('linkclicked', file); + }, + }, +}; + +export default RepoFile; +</script> + +<template> +<tr class="file" v-if="canShowFile" :class="{'active': activeFile.url === file.url}"> + <td @click.prevent="linkClicked(file)"> + <i class="fa file-icon" v-if="!file.loading" :class="file.icon" :style="{'margin-left': file.level * 10 + 'px'}"></i> + <i class="fa fa-spinner fa-spin" v-if="file.loading" :style="{'margin-left': file.level * 10 + 'px'}"></i> + <a :href="file.url" class="repo-file-name" :title="file.url">{{file.name}}</a> + </td> + + <td v-if="!isMini" class="hidden-sm hidden-xs"> + <div class="commit-message"> + <a :href="file.lastCommitUrl">{{file.lastCommitMessage}}</a> + </div> + </td> + + <td v-if="!isMini" class="hidden-xs"> + <span class="commit-update" :title="tooltipTitle(file.lastCommitUpdate)">{{timeFormated(file.lastCommitUpdate)}}</span> + </td> +</tr> +</template> diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue new file mode 100644 index 00000000000..628d02ca704 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_file_buttons.vue @@ -0,0 +1,42 @@ +<script> +import Store from '../stores/repo_store'; +import Helper from '../helpers/repo_helper'; +import RepoMixin from '../mixins/repo_mixin'; + +const RepoFileButtons = { + data: () => Store, + + mixins: [RepoMixin], + + computed: { + + rawDownloadButtonLabel() { + return this.binary ? 'Download' : 'Raw'; + }, + + canPreview() { + return Helper.isKindaBinary(); + }, + }, + + methods: { + rawPreviewToggle: Store.toggleRawPreview, + }, +}; + +export default RepoFileButtons; +</script> + +<template> +<div id="repo-file-buttons" v-if="isMini"> + <a :href="activeFile.raw_path" target="_blank" class="btn btn-default raw" rel="noopener noreferrer">{{rawDownloadButtonLabel}}</a> + + <div class="btn-group" role="group" aria-label="File actions"> + <a :href="activeFile.blame_path" class="btn btn-default blame">Blame</a> + <a :href="activeFile.commits_path" class="btn btn-default history">History</a> + <a :href="activeFile.permalink" class="btn btn-default permalink">Permalink</a> + </div> + + <a href="#" v-if="canPreview" @click.prevent="rawPreviewToggle" class="btn btn-default preview">{{activeFileLabel}}</a> +</div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue new file mode 100644 index 00000000000..ba53ce0eecc --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_file_options.vue @@ -0,0 +1,25 @@ +<script> +const RepoFileOptions = { + props: { + isMini: { + type: Boolean, + required: false, + default: false, + }, + projectName: { + type: String, + required: true, + }, + }, +}; + +export default RepoFileOptions; +</script> + +<template> +<tr v-if="isMini" class="repo-file-options"> + <td> + <span class="title">{{projectName}}</span> + </td> + </tr> +</template> diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue new file mode 100644 index 00000000000..38e9f16d041 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_loading_file.vue @@ -0,0 +1,51 @@ +<script> +const RepoLoadingFile = { + props: { + loading: { + type: Object, + required: false, + default: {}, + }, + hasFiles: { + type: Boolean, + required: false, + default: false, + }, + isMini: { + type: Boolean, + required: false, + default: false, + }, + }, + + methods: { + lineOfCode(n) { + return `line-of-code-${n}`; + }, + }, +}; + +export default RepoLoadingFile; +</script> + +<template> +<tr v-if="loading.tree && !hasFiles" class="loading-file"> + <td> + <div class="animation-container animation-container-small"> + <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div> + </div> + </td> + + <td v-if="!isMini" class="hidden-sm hidden-xs"> + <div class="animation-container"> + <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div> + </div> + </td> + + <td v-if="!isMini" class="hidden-xs"> + <div class="animation-container animation-container-small"> + <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div> + </div> + </td> +</tr> +</template> diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue new file mode 100644 index 00000000000..6a0d684052f --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue @@ -0,0 +1,26 @@ +<script> +const RepoPreviousDirectory = { + props: { + prevUrl: { + type: String, + required: true, + }, + }, + + methods: { + linkClicked(file) { + this.$emit('linkclicked', file); + }, + }, +}; + +export default RepoPreviousDirectory; +</script> + +<template> +<tr class="prev-directory"> + <td colspan="3"> + <a :href="prevUrl" @click.prevent="linkClicked(prevUrl)">..</a> + </td> +</tr> +</template> diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue new file mode 100644 index 00000000000..d8de022335b --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_preview.vue @@ -0,0 +1,32 @@ +<script> +import Store from '../stores/repo_store'; + +export default { + data: () => Store, + mounted() { + $(this.$el).find('.file-content').syntaxHighlight(); + }, + computed: { + html() { + return this.activeFile.html; + }, + }, + + watch: { + html() { + this.$nextTick(() => { + $(this.$el).find('.file-content').syntaxHighlight(); + }); + }, + }, +}; +</script> + +<template> +<div> + <div v-if="!activeFile.render_error" v-html="activeFile.html"></div> + <div v-if="activeFile.render_error" class="vertical-center render-error"> + <p class="text-center">The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.</p> + </div> +</div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue new file mode 100644 index 00000000000..d6d832efc49 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_sidebar.vue @@ -0,0 +1,104 @@ +<script> +import Service from '../services/repo_service'; +import Helper from '../helpers/repo_helper'; +import Store from '../stores/repo_store'; +import RepoPreviousDirectory from './repo_prev_directory.vue'; +import RepoFileOptions from './repo_file_options.vue'; +import RepoFile from './repo_file.vue'; +import RepoLoadingFile from './repo_loading_file.vue'; +import RepoMixin from '../mixins/repo_mixin'; + +const RepoSidebar = { + mixins: [RepoMixin], + components: { + 'repo-file-options': RepoFileOptions, + 'repo-previous-directory': RepoPreviousDirectory, + 'repo-file': RepoFile, + 'repo-loading-file': RepoLoadingFile, + }, + + created() { + this.addPopEventListener(); + }, + + data: () => Store, + + methods: { + addPopEventListener() { + window.addEventListener('popstate', () => { + if (location.href.indexOf('#') > -1) return; + this.linkClicked({ + url: location.href, + }); + }); + }, + + linkClicked(clickedFile) { + let url = ''; + let file = clickedFile; + if (typeof file === 'object') { + file.loading = true; + if (file.type === 'tree' && file.opened) { + file = Store.removeChildFilesOfTree(file); + file.loading = false; + } else { + url = file.url; + Service.url = url; + // I need to refactor this to do the `then` here. + // Not a callback. For now this is good enough. + // it works. + Helper.getContent(file, () => { + file.loading = false; + Helper.scrollTabsRight(); + }); + } + } else if (typeof file === 'string') { + // go back + url = file; + Service.url = url; + Helper.getContent(null, () => Helper.scrollTabsRight()); + } + }, + }, +}; + +export default RepoSidebar; +</script> + +<template> +<div id="sidebar" :class="{'sidebar-mini' : isMini}" v-cloak> + <table class="table"> + <thead v-if="!isMini"> + <tr> + <th class="name">Name</th> + <th class="hidden-sm hidden-xs last-commit">Last Commit</th> + <th class="hidden-xs last-update">Last Update</th> + </tr> + </thead> + <tbody> + <repo-file-options + :is-mini="isMini" + :project-name="projectName"/> + <repo-previous-directory + v-if="isRoot" + :prev-url="prevURL" + @linkclicked="linkClicked(prevURL)"/> + <repo-loading-file + v-for="n in 5" + :key="n" + :loading="loading" + :has-files="!!files.length" + :is-mini="isMini"/> + <repo-file + v-for="file in files" + :key="file.id" + :file="file" + :is-mini="isMini" + @linkclicked="linkClicked(file)" + :is-tree="isTree" + :has-files="!!files.length" + :active-file="activeFile"/> + </tbody> + </table> +</div> +</template> diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue new file mode 100644 index 00000000000..712d64c236f --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_tab.vue @@ -0,0 +1,45 @@ +<script> +import Store from '../stores/repo_store'; + +const RepoTab = { + props: { + tab: { + type: Object, + required: true, + }, + }, + + computed: { + changedClass() { + const tabChangedObj = { + 'fa-times': !this.tab.changed, + 'fa-circle': this.tab.changed, + }; + return tabChangedObj; + }, + }, + + methods: { + tabClicked: Store.setActiveFiles, + + xClicked(file) { + if (file.changed) return; + this.$emit('xclicked', file); + }, + }, +}; + +export default RepoTab; +</script> + +<template> +<li> + <a href="#" class="close" @click.prevent="xClicked(tab)" v-if="!tab.loading"> + <i class="fa" :class="changedClass"></i> + </a> + + <a href="#" class="repo-tab" v-if="!tab.loading" :title="tab.url" @click.prevent="tabClicked(tab)">{{tab.name}}</a> + + <i v-if="tab.loading" class="fa fa-spinner fa-spin"></i> +</li> +</template> diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue new file mode 100644 index 00000000000..907a03e1601 --- /dev/null +++ b/app/assets/javascripts/repo/components/repo_tabs.vue @@ -0,0 +1,43 @@ +<script> +import Vue from 'vue'; +import Store from '../stores/repo_store'; +import RepoTab from './repo_tab.vue'; +import RepoMixin from '../mixins/repo_mixin'; + +const RepoTabs = { + mixins: [RepoMixin], + + components: { + 'repo-tab': RepoTab, + }, + + data: () => Store, + + methods: { + isOverflow() { + return this.$el.scrollWidth > this.$el.offsetWidth; + }, + + xClicked(file) { + Store.removeFromOpenedFiles(file); + }, + }, + + watch: { + openedFiles() { + Vue.nextTick(() => { + this.tabsOverflow = this.isOverflow(); + }); + }, + }, +}; + +export default RepoTabs; +</script> + +<template> +<ul id="tabs" v-if="isMini" v-cloak :class="{'overflown': tabsOverflow}"> + <repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/> + <li class="tabs-divider" /> +</ul> +</template> diff --git a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js new file mode 100644 index 00000000000..8ee2df5c879 --- /dev/null +++ b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js @@ -0,0 +1,21 @@ +/* global monaco */ +import RepoEditor from '../components/repo_editor.vue'; +import Store from '../stores/repo_store'; +import monacoLoader from '../monaco_loader'; + +function repoEditorLoader() { + Store.monacoLoading = true; + return new Promise((resolve, reject) => { + monacoLoader(['vs/editor/editor.main'], () => { + Store.monaco = monaco; + Store.monacoLoading = false; + resolve(RepoEditor); + }, reject); + }); +} + +const MonacoLoaderHelper = { + repoEditorLoader, +}; + +export default MonacoLoaderHelper; diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js new file mode 100644 index 00000000000..fee98c12592 --- /dev/null +++ b/app/assets/javascripts/repo/helpers/repo_helper.js @@ -0,0 +1,303 @@ +/* global Flash */ +import Service from '../services/repo_service'; +import Store from '../stores/repo_store'; +import '../../flash'; + +const RepoHelper = { + getDefaultActiveFile() { + return { + active: true, + binary: false, + extension: '', + html: '', + mime_type: '', + name: '', + plain: '', + size: 0, + url: '', + raw: false, + newContent: '', + changed: false, + loading: false, + }; + }, + + key: '', + + isTree(data) { + return Object.hasOwnProperty.call(data, 'blobs'); + }, + + Time: window.performance + && window.performance.now + ? window.performance + : Date, + + getBranch() { + return $('button.dropdown-menu-toggle').attr('data-ref'); + }, + + getLanguageIDForFile(file, langs) { + const ext = file.name.split('.').pop(); + const foundLang = RepoHelper.findLanguage(ext, langs); + + return foundLang ? foundLang.id : 'plaintext'; + }, + + getFilePathFromFullPath(fullPath, branch) { + return fullPath.split(`${Store.projectUrl}/blob/${branch}`)[1]; + }, + + findLanguage(ext, langs) { + return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1); + }, + + setDirectoryOpen(tree) { + const file = tree; + if (!file) return undefined; + + file.opened = true; + file.icon = 'fa-folder-open'; + RepoHelper.toURL(file.url, file.name); + return file; + }, + + isKindaBinary() { + const okExts = ['md', 'svg']; + return okExts.indexOf(Store.activeFile.extension) > -1; + }, + + setBinaryDataAsBase64(file) { + Service.getBase64Content(file.raw_path) + .then((response) => { + Store.blobRaw = response; + file.base64 = response; // eslint-disable-line no-param-reassign + }) + .catch(RepoHelper.loadingError); + }, + + toggleFakeTab(loading, file) { + if (loading) return Store.addPlaceholderFile(); + return Store.removeFromOpenedFiles(file); + }, + + setLoading(loading, file) { + if (Service.url.indexOf('blob') > -1) { + Store.loading.blob = loading; + return RepoHelper.toggleFakeTab(loading, file); + } + + if (Service.url.indexOf('tree') > -1) Store.loading.tree = loading; + + return undefined; + }, + + getNewMergedList(inDirectory, currentList, newList) { + const newListSorted = newList.sort(this.compareFilesCaseInsensitive); + if (!inDirectory) return newListSorted; + const indexOfFile = currentList.findIndex(file => file.url === inDirectory.url); + if (!indexOfFile) return newListSorted; + return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile); + }, + + mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) { + newList.reverse().forEach((newFile) => { + const fileIndex = indexOfFile + 1; + const file = newFile; + file.level = inDirectory.level + 1; + oldList.splice(fileIndex, 0, file); + }); + + return oldList; + }, + + compareFilesCaseInsensitive(a, b) { + const aName = a.name.toLowerCase(); + const bName = b.name.toLowerCase(); + if (a.level > 0) return 0; + if (aName < bName) { return -1; } + if (aName > bName) { return 1; } + return 0; + }, + + isRoot(url) { + // the url we are requesting -> split by the project URL. Grab the right side. + const isRoot = !!url.split(Store.projectUrl)[1] + // remove the first "/" + .slice(1) + // split this by "/" + .split('/') + // remove the first two items of the array... usually /tree/master. + .slice(2) + // we want to know the length of the array. + // If greater than 0 not root. + .length; + return isRoot; + }, + + getContent(treeOrFile, cb) { + let file = treeOrFile; + // const loadingData = RepoHelper.setLoading(true); + return Service.getContent() + .then((response) => { + const data = response.data; + // RepoHelper.setLoading(false, loadingData); + if (cb) cb(); + Store.isTree = RepoHelper.isTree(data); + if (!Store.isTree) { + if (!file) file = data; + Store.binary = data.binary; + + if (data.binary) { + Store.binaryMimeType = data.mime_type; + // file might be undefined + RepoHelper.setBinaryDataAsBase64(data); + Store.setViewToPreview(); + } else if (!Store.isPreviewView()) { + if (!data.render_error) { + Service.getRaw(data.raw_path) + .then((rawResponse) => { + Store.blobRaw = rawResponse.data; + data.plain = rawResponse.data; + RepoHelper.setFile(data, file); + }).catch(RepoHelper.loadingError); + } + } + + if (Store.isPreviewView()) { + RepoHelper.setFile(data, file); + } + + // if the file tree is empty + if (Store.files.length === 0) { + const parentURL = Service.blobURLtoParentTree(Service.url); + Service.url = parentURL; + RepoHelper.getContent(); + } + } else { + // it's a tree + if (!file) Store.isRoot = RepoHelper.isRoot(Service.url); + file = RepoHelper.setDirectoryOpen(file); + const newDirectory = RepoHelper.dataToListOfFiles(data); + Store.addFilesToDirectory(file, Store.files, newDirectory); + Store.prevURL = Service.blobURLtoParentTree(Service.url); + } + }).catch(RepoHelper.loadingError); + }, + + setFile(data, file) { + const newFile = data; + + newFile.url = file.url || location.pathname; + newFile.url = file.url; + if (newFile.render_error === 'too_large') { + newFile.tooLarge = true; + } + newFile.newContent = ''; + + Store.addToOpenedFiles(newFile); + Store.setActiveFiles(newFile); + }, + + toFA(icon) { + return `fa-${icon}`; + }, + + serializeBlob(blob) { + const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob); + simpleBlob.lastCommitMessage = blob.last_commit.message; + simpleBlob.lastCommitUpdate = blob.last_commit.committed_date; + simpleBlob.loading = false; + + return simpleBlob; + }, + + serializeTree(tree) { + return RepoHelper.serializeRepoEntity('tree', tree); + }, + + serializeSubmodule(submodule) { + return RepoHelper.serializeRepoEntity('submodule', submodule); + }, + + serializeRepoEntity(type, entity) { + const { url, name, icon, last_commit } = entity; + const returnObj = { + type, + name, + url, + icon: RepoHelper.toFA(icon), + level: 0, + loading: false, + }; + + if (entity.last_commit) { + returnObj.lastCommitUrl = `${Store.projectUrl}/commit/${last_commit.id}`; + } else { + returnObj.lastCommitUrl = ''; + } + return returnObj; + }, + + scrollTabsRight() { + // wait for the transition. 0.1 seconds. + setTimeout(() => { + const tabs = document.getElementById('tabs'); + if (!tabs) return; + tabs.scrollLeft = 12000; + }, 200); + }, + + dataToListOfFiles(data) { + const a = []; + + // push in blobs + data.blobs.forEach((blob) => { + a.push(RepoHelper.serializeBlob(blob)); + }); + + data.trees.forEach((tree) => { + a.push(RepoHelper.serializeTree(tree)); + }); + + data.submodules.forEach((submodule) => { + a.push(RepoHelper.serializeSubmodule(submodule)); + }); + + return a; + }, + + genKey() { + return RepoHelper.Time.now().toFixed(3); + }, + + getStateKey() { + return RepoHelper.key; + }, + + setStateKey(key) { + RepoHelper.key = key; + }, + + toURL(url, title) { + const history = window.history; + + RepoHelper.key = RepoHelper.genKey(); + + history.pushState({ key: RepoHelper.key }, '', url); + + if (title) { + document.title = `${title} · GitLab`; + } + }, + + findOpenedFileFromActive() { + return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url); + }, + + loadingError() { + Flash('Unable to load the file at this time.'); + }, +}; + +export default RepoHelper; diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js new file mode 100644 index 00000000000..67c03680fca --- /dev/null +++ b/app/assets/javascripts/repo/index.js @@ -0,0 +1,74 @@ +import $ from 'jquery'; +import Vue from 'vue'; +import Service from './services/repo_service'; +import Store from './stores/repo_store'; +import Repo from './components/repo.vue'; +import RepoEditButton from './components/repo_edit_button.vue'; +import Translate from '../vue_shared/translate'; + +function initDropdowns() { + $('.project-refs-target-form').hide(); + $('.fa-long-arrow-right').hide(); +} + +function addEventsForNonVueEls() { + $(document).on('change', '.dropdown', () => { + Store.targetBranch = $('.project-refs-target-form input[name="ref"]').val(); + }); + + window.onbeforeunload = function confirmUnload(e) { + const hasChanged = Store.openedFiles + .some(file => file.changed); + if (!hasChanged) return undefined; + const event = e || window.event; + if (event) event.returnValue = 'Are you sure you want to lose unsaved changes?'; + // For Safari + return 'Are you sure you want to lose unsaved changes?'; + }; +} + +function setInitialStore(data) { + Store.service = Service; + Store.service.url = data.url; + Store.service.refsUrl = data.refsUrl; + Store.projectId = data.projectId; + Store.projectName = data.projectName; + Store.projectUrl = data.projectUrl; + Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref'); + Store.checkIsCommitable(); +} + +function initRepo(el) { + return new Vue({ + el, + components: { + repo: Repo, + }, + }); +} + +function initRepoEditButton(el) { + return new Vue({ + el, + components: { + repoEditButton: RepoEditButton, + }, + }); +} + +function initRepoBundle() { + const repo = document.getElementById('repo'); + const editButton = document.querySelector('.editable-mode'); + setInitialStore(repo.dataset); + addEventsForNonVueEls(); + initDropdowns(); + + Vue.use(Translate); + + initRepo(repo); + initRepoEditButton(editButton); +} + +$(initRepoBundle); + +export default initRepoBundle; diff --git a/app/assets/javascripts/repo/mixins/repo_mixin.js b/app/assets/javascripts/repo/mixins/repo_mixin.js new file mode 100644 index 00000000000..c8e8238a0d3 --- /dev/null +++ b/app/assets/javascripts/repo/mixins/repo_mixin.js @@ -0,0 +1,17 @@ +import Store from '../stores/repo_store'; + +const RepoMixin = { + computed: { + isMini() { + return !!Store.openedFiles.length; + }, + + changedFiles() { + const changedFileList = this.openedFiles + .filter(file => file.changed); + return changedFileList; + }, + }, +}; + +export default RepoMixin; diff --git a/app/assets/javascripts/repo/monaco_loader.js b/app/assets/javascripts/repo/monaco_loader.js new file mode 100644 index 00000000000..ad1370a7730 --- /dev/null +++ b/app/assets/javascripts/repo/monaco_loader.js @@ -0,0 +1,13 @@ +/* eslint-disable no-underscore-dangle, camelcase */ +/* global __webpack_public_path__ */ + +import monacoContext from 'monaco-editor/dev/vs/loader'; + +monacoContext.require.config({ + paths: { + vs: `${__webpack_public_path__}monaco-editor/vs`, + }, +}); + +window.__monaco_context__ = monacoContext; +export default monacoContext.require; diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js new file mode 100644 index 00000000000..8fba928e456 --- /dev/null +++ b/app/assets/javascripts/repo/services/repo_service.js @@ -0,0 +1,82 @@ +/* global Flash */ +import axios from 'axios'; +import Store from '../stores/repo_store'; +import Api from '../../api'; + +const RepoService = { + url: '', + options: { + params: { + format: 'json', + }, + }, + richExtensionRegExp: /md/, + + checkCurrentBranchIsCommitable() { + const url = Store.service.refsUrl; + return axios.get(url, { params: { + ref: Store.currentBranch, + search: Store.currentBranch, + } }); + }, + + getRaw(url) { + return axios.get(url, { + transformResponse: [res => res], + }); + }, + + buildParams(url = this.url) { + // shallow clone object without reference + const params = Object.assign({}, this.options.params); + + if (this.urlIsRichBlob(url)) params.viewer = 'rich'; + + return params; + }, + + urlIsRichBlob(url = this.url) { + const extension = url.split('.').pop(); + + return this.richExtensionRegExp.test(extension); + }, + + getContent(url = this.url) { + const params = this.buildParams(url); + + return axios.get(url, { + params, + }); + }, + + getBase64Content(url = this.url) { + const request = axios.get(url, { + responseType: 'arraybuffer', + }); + + return request.then(response => this.bufferToBase64(response.data)); + }, + + bufferToBase64(data) { + return new Buffer(data, 'binary').toString('base64'); + }, + + blobURLtoParentTree(url) { + const urlArray = url.split('/'); + urlArray.pop(); + const blobIndex = urlArray.lastIndexOf('blob'); + + if (blobIndex > -1) urlArray[blobIndex] = 'tree'; + + return urlArray.join('/'); + }, + + commitFiles(payload, cb) { + Api.commitMultiple(Store.projectId, payload, (data) => { + Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); + cb(); + }); + }, +}; + +export default RepoService; diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js new file mode 100644 index 00000000000..06ca391ed0c --- /dev/null +++ b/app/assets/javascripts/repo/stores/repo_store.js @@ -0,0 +1,241 @@ +/* global Flash */ +import Helper from '../helpers/repo_helper'; +import Service from '../services/repo_service'; + +const RepoStore = { + ideEl: {}, + monaco: {}, + monacoLoading: false, + monacoInstance: {}, + service: '', + editor: '', + sidebar: '', + editMode: false, + isTree: false, + isRoot: false, + prevURL: '', + projectId: '', + projectName: '', + projectUrl: '', + trees: [], + blobs: [], + submodules: [], + blobRaw: '', + blobRendered: '', + currentBlobView: 'repo-preview', + openedFiles: [], + tabSize: 100, + defaultTabSize: 100, + minTabSize: 30, + tabsOverflow: 41, + submitCommitsLoading: false, + binaryLoaded: false, + dialog: { + open: false, + title: '', + status: false, + }, + activeFile: Helper.getDefaultActiveFile(), + activeFileIndex: 0, + activeLine: 0, + activeFileLabel: 'Raw', + files: [], + isCommitable: false, + binary: false, + currentBranch: '', + targetBranch: 'new-branch', + commitMessage: '', + binaryMimeType: '', + // scroll bar space for windows + scrollWidth: 0, + binaryTypes: { + png: false, + md: false, + svg: false, + unknown: false, + }, + loading: { + tree: false, + blob: false, + }, + readOnly: true, + + resetBinaryTypes() { + Object.keys(RepoStore.binaryTypes).forEach((key) => { + RepoStore.binaryTypes[key] = false; + }); + }, + + // mutations + checkIsCommitable() { + RepoStore.service.checkCurrentBranchIsCommitable() + .then((data) => { + // you shouldn't be able to make commits on commits or tags. + const { Branches, Commits, Tags } = data.data; + if (Branches && Branches.length) RepoStore.isCommitable = true; + if (Commits && Commits.length) RepoStore.isCommitable = false; + if (Tags && Tags.length) RepoStore.isCommitable = false; + }).catch(() => Flash('Failed to check if branch can be committed to.')); + }, + + addFilesToDirectory(inDirectory, currentList, newList) { + RepoStore.files = Helper.getNewMergedList(inDirectory, currentList, newList); + }, + + toggleRawPreview() { + RepoStore.activeFile.raw = !RepoStore.activeFile.raw; + RepoStore.activeFileLabel = RepoStore.activeFile.raw ? 'Display rendered file' : 'Display source'; + }, + + setActiveFiles(file) { + if (RepoStore.isActiveFile(file)) return; + RepoStore.openedFiles = RepoStore.openedFiles + .map((openedFile, i) => RepoStore.setFileActivity(file, openedFile, i)); + + RepoStore.setActiveToRaw(); + + if (file.binary) { + RepoStore.blobRaw = file.base64; + RepoStore.binaryMimeType = file.mime_type; + } else if (file.newContent || file.plain) { + RepoStore.blobRaw = file.newContent || file.plain; + } else { + Service.getRaw(file.raw_path) + .then((rawResponse) => { + RepoStore.blobRaw = rawResponse.data; + Helper.findOpenedFileFromActive().plain = rawResponse.data; + }).catch(Helper.loadingError); + } + + if (!file.loading) Helper.toURL(file.url, file.name); + RepoStore.binary = file.binary; + }, + + setFileActivity(file, openedFile, i) { + const activeFile = openedFile; + activeFile.active = file.url === activeFile.url; + + if (activeFile.active) RepoStore.setActiveFile(activeFile, i); + + return activeFile; + }, + + setActiveFile(activeFile, i) { + RepoStore.activeFile = Object.assign({}, RepoStore.activeFile, activeFile); + RepoStore.activeFileIndex = i; + }, + + setActiveToRaw() { + RepoStore.activeFile.raw = false; + // can't get vue to listen to raw for some reason so RepoStore for now. + RepoStore.activeFileLabel = 'Display source'; + }, + + removeChildFilesOfTree(tree) { + let foundTree = false; + const treeToClose = tree; + let wereDone = false; + RepoStore.files = RepoStore.files.filter((file) => { + const isItTheTreeWeWant = file.url === treeToClose.url; + // if it's the next tree + if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) { + wereDone = true; + return true; + } + if (wereDone) return true; + + if (isItTheTreeWeWant) foundTree = true; + + if (foundTree) return file.level <= treeToClose.level; + return true; + }); + + treeToClose.opened = false; + treeToClose.icon = 'fa-folder'; + return treeToClose; + }, + + removeFromOpenedFiles(file) { + if (file.type === 'tree') return; + let foundIndex; + RepoStore.openedFiles = RepoStore.openedFiles.filter((openedFile, i) => { + if (openedFile.url === file.url) foundIndex = i; + return openedFile.url !== file.url; + }); + + // now activate the right tab based on what you closed. + if (RepoStore.openedFiles.length === 0) { + RepoStore.activeFile = {}; + return; + } + + if (RepoStore.openedFiles.length === 1 || foundIndex === 0) { + RepoStore.setActiveFiles(RepoStore.openedFiles[0]); + return; + } + + if (foundIndex) { + if (foundIndex > 0) { + RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]); + } + } + }, + + addPlaceholderFile() { + const randomURL = Helper.Time.now(); + const newFakeFile = { + active: false, + binary: true, + type: 'blob', + loading: true, + mime_type: 'loading', + name: 'loading', + url: randomURL, + fake: true, + }; + + RepoStore.openedFiles.push(newFakeFile); + + return newFakeFile; + }, + + addToOpenedFiles(file) { + const openFile = file; + + const openedFilesAlreadyExists = RepoStore.openedFiles + .some(openedFile => openedFile.url === openFile.url); + + if (openedFilesAlreadyExists) return; + + openFile.changed = false; + RepoStore.openedFiles.push(openFile); + }, + + setActiveFileContents(contents) { + if (!RepoStore.editMode) return; + const currentFile = RepoStore.openedFiles[RepoStore.activeFileIndex]; + RepoStore.activeFile.newContent = contents; + RepoStore.activeFile.changed = RepoStore.activeFile.plain !== RepoStore.activeFile.newContent; + currentFile.changed = RepoStore.activeFile.changed; + currentFile.newContent = contents; + }, + + toggleBlobView() { + RepoStore.currentBlobView = RepoStore.isPreviewView() ? 'repo-editor' : 'repo-preview'; + }, + + setViewToPreview() { + RepoStore.currentBlobView = 'repo-preview'; + }, + + // getters + + isActiveFile(file) { + return file && file.url === RepoStore.activeFile.url; + }, + + isPreviewView() { + return RepoStore.currentBlobView === 'repo-preview'; + }, +}; +export default RepoStore; diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index d8f1fe10b26..fa958d75fa4 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */ +import _ from 'underscore'; import Cookies from 'js-cookie'; import SidebarHeightManager from './sidebar_height_manager'; diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 51448252c0f..0be141eb5f9 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -3,6 +3,7 @@ /* global ShortcutsNavigation */ /* global sidebar */ +import _ from 'underscore'; import 'mousetrap'; import './shortcuts_navigation'; @@ -58,7 +59,7 @@ import './shortcuts_navigation'; }); // If replyField already has some content, add a newline before our quote separator = replyField.val().trim() !== "" && "\n\n" || ''; - replyField.val(function(_, current) { + replyField.val(function(a, current) { return current + separator + quote.join('') + "\n"; }); diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue new file mode 100644 index 00000000000..422c02c7b7e --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -0,0 +1,82 @@ +<script> +/* global Flash */ +import editForm from './edit_form.vue'; + +export default { + components: { + editForm, + }, + props: { + isConfidential: { + required: true, + type: Boolean, + }, + isEditable: { + required: true, + type: Boolean, + }, + service: { + required: true, + type: Object, + }, + }, + data() { + return { + edit: false, + }; + }, + computed: { + faEye() { + const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye'; + return { + [eye]: true, + }; + }, + }, + methods: { + toggleForm() { + this.edit = !this.edit; + }, + updateConfidentialAttribute(confidential) { + this.service.update('issue', { confidential }) + .then(() => location.reload()) + .catch(() => new Flash('Something went wrong trying to change the confidentiality of this issue')); + }, + }, +}; +</script> + +<template> + <div class="block confidentiality"> + <div class="sidebar-collapsed-icon"> + <i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i> + </div> + <div class="title hide-collapsed"> + Confidentiality + <a + v-if="isEditable" + class="pull-right confidential-edit" + href="#" + @click.prevent="toggleForm" + > + Edit + </a> + </div> + <div class="value confidential-value hide-collapsed"> + <editForm + v-if="edit" + :toggle-form="toggleForm" + :is-confidential="isConfidential" + :update-confidential-attribute="updateConfidentialAttribute" + /> + <div v-if="!isConfidential" class="no-value confidential-value"> + <i class="fa fa-eye is-not-confidential"></i> + None + </div> + <div v-else class="value confidential-value hide-collapsed"> + <i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i> + This issue is confidential + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue new file mode 100644 index 00000000000..d578b663a54 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue @@ -0,0 +1,47 @@ +<script> +import editFormButtons from './edit_form_buttons.vue'; + +export default { + components: { + editFormButtons, + }, + props: { + isConfidential: { + required: true, + type: Boolean, + }, + toggleForm: { + required: true, + type: Function, + }, + updateConfidentialAttribute: { + required: true, + type: Function, + }, + }, +}; +</script> + +<template> + <div class="dropdown open"> + <div class="dropdown-menu confidential-warning-message"> + <div> + <p v-if="!isConfidential"> + You are going to turn on the confidentiality. This means that only team members with + <strong>at least Reporter access</strong> + are able to see and leave comments on the issue. + </p> + <p v-else> + You are going to turn off the confidentiality. This means + <strong>everyone</strong> + will be able to see and leave a comment on this issue. + </p> + <edit-form-buttons + :is-confidential="isConfidential" + :toggle-form="toggleForm" + :update-confidential-attribute="updateConfidentialAttribute" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue new file mode 100644 index 00000000000..97af4a3f505 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue @@ -0,0 +1,45 @@ +<script> +export default { + props: { + isConfidential: { + required: true, + type: Boolean, + }, + toggleForm: { + required: true, + type: Function, + }, + updateConfidentialAttribute: { + required: true, + type: Function, + }, + }, + computed: { + onOrOff() { + return this.isConfidential ? 'Turn Off' : 'Turn On'; + }, + updateConfidentialBool() { + return !this.isConfidential; + }, + }, +}; +</script> + +<template> + <div class="confidential-warning-message-actions"> + <button + type="button" + class="btn btn-default append-right-10" + @click="toggleForm" + > + Cancel + </button> + <button + type="button" + class="btn btn-close" + @click.prevent="updateConfidentialAttribute(updateConfidentialBool)" + > + {{ onOrOff }} + </button> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js index 650e935b116..2d682215cf8 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + import '~/smart_interval'; import timeTracker from './time_tracker'; diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js index a9df66748c5..9edded3ead6 100644 --- a/app/assets/javascripts/sidebar/sidebar_bundle.js +++ b/app/assets/javascripts/sidebar/sidebar_bundle.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; import sidebarAssignees from './components/assignees/sidebar_assignees'; +import confidential from './components/confidential/confidential_issue_sidebar.vue'; import Mediator from './sidebar_mediator'; @@ -10,13 +11,28 @@ function domContentLoaded() { mediator.fetch(); const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees'); - + const confidentialEl = document.querySelector('#js-confidential-entry-point'); // Only create the sidebarAssignees vue app if it is found in the DOM // We currently do not use sidebarAssignees for the MR page if (sidebarAssigneesEl) { new Vue(sidebarAssignees).$mount(sidebarAssigneesEl); } + if (confidentialEl) { + const dataNode = document.getElementById('js-confidential-issue-data'); + const initialData = JSON.parse(dataNode.innerHTML); + + const ConfidentialComp = Vue.extend(confidential); + + new ConfidentialComp({ + propsData: { + isConfidential: initialData.is_confidential, + isEditable: initialData.is_editable, + service: mediator.service, + }, + }).$mount(confidentialEl); + } + new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker'); } diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js index 022415f22b2..df19d7305f8 100644 --- a/app/assets/javascripts/sidebar_height_manager.js +++ b/app/assets/javascripts/sidebar_height_manager.js @@ -1,3 +1,5 @@ +import _ from 'underscore'; + export default { init() { if (!this.initialized) { @@ -30,4 +32,3 @@ export default { } }, }; - diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js index ef401abce2d..8875590f0f2 100644 --- a/app/assets/javascripts/test_utils/index.js +++ b/app/assets/javascripts/test_utils/index.js @@ -1,3 +1,5 @@ +import 'core-js/es6/map'; +import 'core-js/es6/set'; import simulateDrag from './simulate_drag'; // Export to global space for rspec to use diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index bba8b5abbb4..a606852c22c 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -52,6 +52,7 @@ export default class Todos { } updateRowStateClicked(e) { + e.stopPropagation(); e.preventDefault(); const target = e.target; @@ -92,6 +93,7 @@ export default class Todos { } updateAllStateClicked(e) { + e.stopPropagation(); e.preventDefault(); const target = e.currentTarget; @@ -142,6 +144,7 @@ export default class Todos { if (gl.utils.isMetaClick(e)) { const windowTarget = '_blank'; const selected = e.target; + e.stopPropagation(); e.preventDefault(); if (selected.tagName === 'IMG') { diff --git a/app/assets/javascripts/two_factor_auth.js b/app/assets/javascripts/two_factor_auth.js new file mode 100644 index 00000000000..d26f61562a5 --- /dev/null +++ b/app/assets/javascripts/two_factor_auth.js @@ -0,0 +1,13 @@ +/* global U2FRegister */ +document.addEventListener('DOMContentLoaded', () => { + const twoFactorNode = document.querySelector('.js-two-factor-auth'); + const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; + if (skippable) { + const button = `<a class="btn btn-xs btn-warning pull-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; + const flashAlert = document.querySelector('.flash-alert .container-fluid'); + if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); + } + + const u2fRegister = new U2FRegister($('#js-register-u2f'), gon.u2f); + u2fRegister.start(); +}); diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index cd5280948fd..8821b22477f 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -3,6 +3,8 @@ /* global U2FError */ /* global U2FUtil */ +import _ from 'underscore'; + // Authenticate U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> authenticated -> POST to server diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 1234d17b8fd..3a2534d553b 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -3,6 +3,8 @@ /* global U2FError */ /* global U2FUtil */ +import _ from 'underscore'; + // Register U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> registered -> POST to server diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js new file mode 100644 index 00000000000..f503076715c --- /dev/null +++ b/app/assets/javascripts/ui_development_kit.js @@ -0,0 +1,22 @@ +import Api from './api'; + +document.addEventListener('DOMContentLoaded', () => { + $('#js-project-dropdown').glDropdown({ + data: (term, callback) => { + Api.projects(term, { + order_by: 'last_activity_at', + }, (data) => { + callback(data); + }); + }, + text: project => (project.name_with_namespace || project.name), + selectable: true, + fieldName: 'author_id', + filterable: true, + search: { + fields: ['name_with_namespace'], + }, + id: data => data.id, + isSelected: data => (data.id === 2), + }); +}); diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js index a348d69153c..bb34d5d2008 100644 --- a/app/assets/javascripts/username_validator.js +++ b/app/assets/javascripts/username_validator.js @@ -1,5 +1,7 @@ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ +import _ from 'underscore'; + const debounceTimeoutDuration = 1000; const invalidInputClass = 'gl-field-error-outline'; const successInputClass = 'gl-field-success-outline'; diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js index f091e319f44..5e947769f8a 100644 --- a/app/assets/javascripts/users/activity_calendar.js +++ b/app/assets/javascripts/users/activity_calendar.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import d3 from 'd3'; const LOADING_HTML = ` @@ -6,6 +7,14 @@ const LOADING_HTML = ` </div> `; +function getSystemDate(systemUtcOffsetSeconds) { + const date = new Date(); + const localUtcOffsetMinutes = 0 - date.getTimezoneOffset(); + const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60; + date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes); + return date; +} + function formatTooltipText({ date, count }) { const dateObject = new Date(date); const dateDayName = gl.utils.getDayName(dateObject); @@ -21,7 +30,7 @@ function formatTooltipText({ date, count }) { const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); export default class ActivityCalendar { - constructor(container, timestamps, calendarActivitiesPath) { + constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) { this.calendarActivitiesPath = calendarActivitiesPath; this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; @@ -36,7 +45,7 @@ export default class ActivityCalendar { this.timestampsTmp = []; let group = 0; - const today = new Date(); + const today = getSystemDate(utcOffset); today.setHours(0, 0, 0, 0, 0); const oneYearAgo = new Date(today); diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js index 5fe6603ce7b..1215b265e28 100644 --- a/app/assets/javascripts/users/user_tabs.js +++ b/app/assets/javascripts/users/user_tabs.js @@ -150,15 +150,21 @@ export default class UserTabs { const $calendarWrap = this.$parentEl.find('.user-calendar'); const calendarPath = $calendarWrap.data('calendarPath'); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); + const utcOffset = $calendarWrap.data('utcOffset'); + let utcFormatted = 'UTC'; + if (utcOffset !== 0) { + utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; + } $.ajax({ dataType: 'json', url: calendarPath, success: (activityData) => { $calendarWrap.html(CALENDAR_TEMPLATE); + $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); // eslint-disable-next-line no-new - new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath); + new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset); }, }); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 5728afb4c59..16ebf5916dc 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ /* global Issuable */ /* global emitSidebarEvent */ +import _ from 'underscore'; // TODO: remove eventHub hack after code splitting refactor window.emitSidebarEvent = window.emitSidebarEvent || $.noop; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js index a01cb8cc202..982b5e8e373 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js @@ -1,3 +1,5 @@ +import tooltip from '../../vue_shared/directives/tooltip'; + export default { name: 'MRWidgetAuthor', props: { @@ -5,11 +7,14 @@ export default { showAuthorName: { type: Boolean, required: false, default: true }, showAuthorTooltip: { type: Boolean, required: false, default: false }, }, + directives: { + tooltip, + }, template: ` <a :href="author.webUrl || author.web_url" - class="author-link" - :class="{ 'has-tooltip': showAuthorTooltip }" + class="author-link inline" + :v-tooltip="showAuthorTooltip" :title="author.name"> <img :src="author.avatarUrl || author.avatar_url" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js index 744a1cd24fa..e98d147733c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js @@ -1,8 +1,8 @@ /* global Flash */ import '~/lib/utils/datetime_utility'; -import { statusIconEntityMap } from '../../vue_shared/ci_status_icons'; import MemoryUsage from './mr_widget_memory_usage'; +import StatusIcon from './mr_widget_status_icon'; import MRWidgetService from '../services/mr_widget_service'; export default { @@ -13,11 +13,7 @@ export default { }, components: { 'mr-widget-memory-usage': MemoryUsage, - }, - computed: { - svg() { - return statusIconEntityMap.icon_status_success; - }, + 'status-icon': StatusIcon, }, methods: { formatDate(date) { @@ -51,51 +47,51 @@ export default { }, }, template: ` - <div class="mr-widget-heading"> + <div class="mr-widget-heading deploy-heading"> <div v-for="deployment in mr.deployments"> - <div class="ci-widget"> + <div class="ci-widget media"> <div class="ci-status-icon ci-status-icon-success"> <span class="js-icon-link icon-link"> - <span class="ci-status-icon" - v-html="svg" - aria-hidden="true"></span> + <status-icon status="success" /> </span> </div> - <span> - <span - v-if="hasDeploymentMeta(deployment)"> - Deployed to - </span> - <a - v-if="hasDeploymentMeta(deployment)" - :href="deployment.url" - target="_blank" - rel="noopener noreferrer nofollow" - class="js-deploy-meta"> - {{deployment.name}} - </a> - <span - v-if="hasExternalUrls(deployment)"> - on - </span> - <a - v-if="hasExternalUrls(deployment)" - :href="deployment.external_url" - target="_blank" - rel="noopener noreferrer nofollow" - class="js-deploy-url"> - <i - class="fa fa-external-link" - aria-hidden="true" /> - {{deployment.external_url_formatted}} - </a> - <span - v-if="hasDeploymentTime(deployment)" - :data-title="deployment.deployed_at_formatted" - class="js-deploy-time" - data-toggle="tooltip" - data-placement="top"> - {{formatDate(deployment.deployed_at)}} + <div class="media-body space-children"> + <span> + <span + v-if="hasDeploymentMeta(deployment)"> + Deployed to + </span> + <a + v-if="hasDeploymentMeta(deployment)" + :href="deployment.url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-meta inline"> + {{deployment.name}} + </a> + <span + v-if="hasExternalUrls(deployment)"> + on + </span> + <a + v-if="hasExternalUrls(deployment)" + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-url inline"> + <i + class="fa fa-external-link" + aria-hidden="true" /> + {{deployment.external_url_formatted}} + </a> + <span + v-if="hasDeploymentTime(deployment)" + :data-title="deployment.deployed_at_formatted" + class="js-deploy-time" + data-toggle="tooltip" + data-placement="top"> + {{formatDate(deployment.deployed_at)}} + </span> </span> <button type="button" @@ -104,13 +100,13 @@ export default { class="btn btn-default btn-xs"> Stop environment </button> - </span> + <mr-widget-memory-usage + v-if="deployment.metrics_url" + :metrics-url="deployment.metrics_url" + :metrics-monitoring-url="deployment.metrics_monitoring_url" + /> + </div> </div> - <mr-widget-memory-usage - v-if="deployment.metrics_url" - :metrics-url="deployment.metrics_url" - :metrics-monitoring-url="deployment.metrics_monitoring_url" - /> </div> </div> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js index 8430548903c..c05a76a3b4a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js @@ -1,3 +1,4 @@ +import tooltip from '../../vue_shared/directives/tooltip'; import '../../lib/utils/text_utility'; export default { @@ -5,6 +6,9 @@ export default { props: { mr: { type: Object, required: true }, }, + directives: { + tooltip, + }, computed: { shouldShowCommitsBehindText() { return this.mr.divergedCommitsCount > 0; @@ -29,18 +33,51 @@ export default { }, template: ` <div class="mr-source-target"> - <div - v-if="mr.isOpen" - class="pull-right"> + <div class="normal"> + <strong> + Request to merge + <span + class="label-branch" + :class="{'label-truncated': isBranchTitleLong(mr.sourceBranch)}" + :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''" + data-placement="bottom" + :v-tooltip="isBranchTitleLong(mr.sourceBranch)" + v-html="mr.sourceBranchLink"></span> + <button + v-tooltip + class="btn btn-transparent btn-clipboard" + data-title="Copy branch name to clipboard" + :data-clipboard-text="branchNameClipboardData"> + <i + aria-hidden="true" + class="fa fa-clipboard"></i> + </button> + into + <span + class="label-branch" + :v-tooltip="isBranchTitleLong(mr.sourceBranch)" + :class="{'label-truncatedtooltip': isBranchTitleLong(mr.targetBranch)}" + :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''" + data-placement="bottom"> + <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a> + </span> + </strong> + <span + v-if="shouldShowCommitsBehindText" + class="diverged-commits-count"> + (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>) + </span> + </div> + <div v-if="mr.isOpen"> <a href="#modal_merge_info" data-toggle="modal" - class="btn inline btn-grouped btn-sm"> + class="btn btn-small inline"> Check out branch </a> - <span class="dropdown inline prepend-left-5"> + <span class="dropdown inline prepend-left-10"> <a - class="btn btn-sm dropdown-toggle" + class="btn btn-xs dropdown-toggle" data-toggle="dropdown" aria-label="Download as" role="button"> @@ -69,38 +106,6 @@ export default { </ul> </span> </div> - <div class="normal"> - <strong> - Request to merge - <span - class="label-branch" - :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.sourceBranch)}" - :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''" - data-placement="bottom" - v-html="mr.sourceBranchLink"></span> - <button - class="btn btn-transparent btn-clipboard has-tooltip" - data-title="Copy branch name to clipboard" - :data-clipboard-text="branchNameClipboardData"> - <i - aria-hidden="true" - class="fa fa-clipboard"></i> - </button> - into - <span - class="label-branch" - :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}" - :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''" - data-placement="bottom"> - <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a> - </span> - </strong> - <span - v-if="shouldShowCommitsBehindText" - class="diverged-commits-count"> - (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>) - </span> - </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index 534e2a88eff..a4e34116c33 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -120,13 +120,12 @@ export default { }, template: ` <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage"> - <div class="legend"></div> <p v-if="shouldShowLoading" class="usage-info js-usage-info usage-info-loading"> <i class="fa fa-spinner fa-spin usage-info-load-spinner" - aria-hidden="true" />Loading deployment statistics. + aria-hidden="true" />Loading deployment statistics </p> <p v-if="shouldShowMemoryGraph" @@ -136,12 +135,12 @@ export default { <p v-if="shouldShowLoadFailure" class="usage-info js-usage-info usage-info-failed"> - Failed to load deployment statistics. + Failed to load deployment statistics </p> <p v-if="shouldShowMetricsUnavailable" class="usage-info js-usage-info usage-info-unavailable"> - Deployment statistics are not available currently. + Deployment statistics are not available currently </p> <mr-memory-graph v-if="shouldShowMemoryGraph" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js index 2fecebce7a0..1d9f9863dd9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js @@ -16,7 +16,7 @@ export default { <a data-toggle="modal" href="#modal_merge_info"> - command line. + command line </a> </section> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index c02e10128e2..6c2e9ba1d30 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -29,58 +29,55 @@ export default { }, template: ` <div class="mr-widget-heading"> - <div class="ci-widget"> + <div class="ci-widget media"> <template v-if="hasCIError"> - <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error"> - <span class="js-icon-link icon-link"> - <span - v-html="svg" - aria-hidden="true"></span> - </span> + <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> + <span + v-html="svg" + aria-hidden="true"></span> + </div> + <div class="media-body"> + Could not connect to the CI server. Please check your settings and try again </div> - <span>Could not connect to the CI server. Please check your settings and try again.</span> </template> <template v-else> - <div> + <div class="ci-status-icon append-right-10"> <a class="icon-link" :href="this.status.details_path"> <ci-icon :status="status" /> </a> </div> - <span> - Pipeline - <a - :href="mr.pipeline.path" - class="pipeline-id">#{{mr.pipeline.id}}</a> - {{mr.pipeline.details.status.label}} - </span> - <span - v-if="mr.pipeline.details.stages.length > 0"> - with {{stageText}} - </span> - <div class="mr-widget-pipeline-graph"> - <div class="stage-cell"> - <div - v-if="mr.pipeline.details.stages.length > 0" - v-for="stage in mr.pipeline.details.stages" - class="stage-container dropdown js-mini-pipeline-graph"> - <pipeline-stage :stage="stage" /> - </div> - </div> + <div class="media-body"> + <span> + Pipeline + <a + :href="mr.pipeline.path" + class="pipeline-id">#{{mr.pipeline.id}}</a> + </span> + <span class="mr-widget-pipeline-graph"> + <span class="stage-cell"> + <div + v-if="mr.pipeline.details.stages.length > 0" + v-for="stage in mr.pipeline.details.stages" + class="stage-container dropdown js-mini-pipeline-graph"> + <pipeline-stage :stage="stage" /> + </div> + </span> + </span> + <span> + {{mr.pipeline.details.status.label}} for + <a + :href="mr.pipeline.commit.commit_path" + class="commit-sha js-commit-link"> + {{mr.pipeline.commit.short_id}}</a>. + </span> + <span + v-if="mr.pipeline.coverage" + class="js-mr-coverage"> + Coverage {{mr.pipeline.coverage}}% + </span> </div> - <span> - for - <a - :href="mr.pipeline.commit.commit_path" - class="commit-sha js-commit-link"> - {{mr.pipeline.commit.short_id}}</a>. - </span> - <span - v-if="mr.pipeline.coverage" - class="js-mr-coverage"> - Coverage {{mr.pipeline.coverage}}%. - </span> </template> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js index 205804670fa..563267ad044 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js @@ -2,37 +2,32 @@ export default { name: 'MRWidgetRelatedLinks', props: { relatedLinks: { type: Object, required: true }, + state: { type: String, required: false }, }, computed: { hasLinks() { const { closing, mentioned, assignToMe } = this.relatedLinks; return closing || mentioned || assignToMe; }, - }, - methods: { - hasMultipleIssues(text) { - return !text ? false : text.match(/<\/a> and <a/); - }, - issueLabel(field) { - return this.hasMultipleIssues(this.relatedLinks[field]) ? 'issues' : 'issue'; - }, - verbLabel(field) { - return this.hasMultipleIssues(this.relatedLinks[field]) ? 'are' : 'is'; + closesText() { + if (this.state === 'merged') { + return 'Closed'; + } + if (this.state === 'closed') { + return 'Did not close'; + } + return 'Closes'; }, }, template: ` <section v-if="hasLinks" class="mr-info-list mr-links"> - <div class="legend"></div> <p v-if="relatedLinks.closing"> - Closes {{issueLabel('closing')}} - <span v-html="relatedLinks.closing"></span>. + {{closesText}} <span v-html="relatedLinks.closing"></span> </p> <p v-if="relatedLinks.mentioned"> - <span class="capitalize">{{issueLabel('mentioned')}}</span> - <span v-html="relatedLinks.mentioned"></span> - {{verbLabel('mentioned')}} mentioned but will not be closed. + Mentions <span v-html="relatedLinks.mentioned"></span> </p> <p v-if="relatedLinks.assignToMe"> <span v-html="relatedLinks.assignToMe"></span> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js new file mode 100644 index 00000000000..b01c923311b --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js @@ -0,0 +1,36 @@ +import ciIcon from '../../vue_shared/components/ci_icon.vue'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + +export default { + props: { + status: { type: String, required: true }, + showDisabledButton: { type: Boolean, required: false }, + }, + components: { + ciIcon, + loadingIcon, + }, + computed: { + statusObj() { + return { + group: this.status, + icon: `icon_status_${this.status}`, + }; + }, + }, + template: ` + <div class="space-children flex-container-block append-right-10"> + <div v-if="status === 'loading'" class="mr-widget-icon"> + <loading-icon /> + </div> + <ci-icon v-else :status="statusObj" /> + <button + v-if="showDisabledButton" + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js index c7f25a1697c..2b16a2d6817 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js @@ -1,16 +1,26 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetArchived', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - This project is archived, write access has been disabled. - </span> + <div class="mr-widget-body media"> + <div class="space-children"> + <status-icon status="failed" /> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + </div> + <div class="media-body"> + <span class="bold"> + This project is archived, write access has been disabled + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js index 4063859d5d0..5648208f7b1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js @@ -1,4 +1,5 @@ import eventHub from '../../event_hub'; +import statusIcon from '../mr_widget_status_icon'; export default { name: 'MRWidgetAutoMergeFailed', @@ -10,6 +11,9 @@ export default { isRefreshing: false, }; }, + components: { + statusIcon, + }, methods: { refreshWidget() { this.isRefreshing = true; @@ -19,18 +23,16 @@ export default { }, }, template: ` - <div class="mr-widget-body"> - <button - class="btn btn-success btn-small" - disabled="true" - type="button"> - Merge - </button> - <span class="bold danger"> - This merge request failed to be merged automatically. + <div class="mr-widget-body media"> + <status-icon status="failed" /> + <div class="media-body space-children"> + <span class="bold"> + <template v-if="mr.mergeError">{{mr.mergeError}}.</template> + This merge request failed to be merged automatically + </span> <button @click="refreshWidget" - :class="{ disabled: isRefreshing }" + :disabled="isRefreshing" type="button" class="btn btn-xs btn-default"> <i @@ -39,9 +41,6 @@ export default { aria-hidden="true" /> Refresh </button> - </span> - <div class="merge-error-text danger bold"> - {{mr.mergeError}} </div> </div> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js index 8515b54e62d..aaf9d3304a4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js @@ -1,19 +1,18 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetChecking', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - Checking ability to merge automatically. - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - </span> + <div class="mr-widget-body media"> + <status-icon status="loading" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + Checking ability to merge automatically + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js index fc2e42c6821..4078aad7f83 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js @@ -1,4 +1,5 @@ import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; +import statusIcon from '../mr_widget_status_icon'; export default { name: 'MRWidgetClosed', @@ -7,24 +8,28 @@ export default { }, components: { 'mr-widget-author-and-time': mrWidgetAuthorTime, + statusIcon, }, template: ` - <div class="mr-widget-body"> - <mr-widget-author-and-time - actionText="Closed by" - :author="mr.closedBy" - :dateTitle="mr.updatedAt" - :dateReadable="mr.closedAt" - /> - <section> - <p> - The changes were not merged into - <a - :href="mr.targetBranchPath" - class="label-branch"> - {{mr.targetBranch}}</a>. - </p> - </section> + <div class="mr-widget-body media"> + <status-icon status="failed" /> + <div class="media-body"> + <mr-widget-author-and-time + actionText="Closed by" + :author="mr.closedBy" + :dateTitle="mr.updatedAt" + :dateReadable="mr.closedAt" + /> + <section class="mr-info-list"> + <p> + The changes were not merged into + <a + :href="mr.targetBranchPath" + class="label-branch"> + {{mr.targetBranch}}</a> + </p> + </section> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js index 36596c6f37e..f9cb79a0bc1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js @@ -1,27 +1,25 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetConflicts', props: { mr: { type: Object, required: true }, }, + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - There are merge conflicts. - <span v-if="!mr.canMerge"> - Resolve these conflicts or ask someone with write access to this repository to merge it locally. + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + There are merge conflicts<span v-if="!mr.canMerge">.</span> + <span v-if="!mr.canMerge"> + Resolve these conflicts or ask someone with write access to this repository to merge it locally + </span> </span> - </span> - <div - v-if="mr.canMerge" - class="btn-group"> <a - v-if="mr.conflictResolutionPath" + v-if="mr.canMerge && mr.conflictResolutionPath" :href="mr.conflictResolutionPath" class="btn btn-default btn-xs js-resolve-conflicts-button"> Resolve conflicts diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js index 600b4d42e3d..1cb24549d53 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js @@ -1,3 +1,4 @@ +import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; export default { @@ -38,39 +39,40 @@ export default { } }, }, + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - class="btn btn-success btn-small" - disabled="true" - type="button"> - Merge - </button> - <span - v-if="!isRefreshing" - class="bold danger"> - <span - class="has-error-message" - v-if="mr.mergeError"> - {{mr.mergeError}} - </span> - <span v-else>Merge failed.</span> - <span - :class="{ 'has-custom-error': mr.mergeError }"> - Refreshing in {{timerText}} to show the updated status... + <div class="mr-widget-body media"> + <template v-if="isRefreshing"> + <status-icon status="loading" /> + <span class="media-body bold js-refresh-label"> + Refreshing now </span> - <button - @click="refresh" - class="btn btn-default btn-xs js-refresh-button" - type="button"> - Refresh now - </button> - </span> - <span - v-if="isRefreshing" - class="bold js-refresh-label"> - Refreshing now... - </span> + </template> + <template v-else> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + <span + class="has-error-message" + v-if="mr.mergeError"> + {{mr.mergeError}}. + </span> + <span v-else>Merge failed.</span> + <span + :class="{ 'has-custom-error': mr.mergeError }"> + Refreshing in {{timerText}} to show the updated status... + </span> + </span> + <button + @click="refresh" + class="btn btn-default btn-xs js-refresh-button" + type="button"> + Refresh now + </button> + </div> + </template> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js deleted file mode 100644 index 0bd31731a0b..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js +++ /dev/null @@ -1,24 +0,0 @@ -export default { - name: 'MRWidgetLocked', - props: { - mr: { type: Object, required: true }, - }, - template: ` - <div class="mr-widget-body mr-state-locked"> - <span class="state-label">Locked</span> - This merge request is in the process of being merged, during which time it is locked and cannot be closed. - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - <section class="mr-info-list mr-links"> - <div class="legend"></div> - <p> - The changes will be merged into - <span class="label-branch"> - <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> - </span>. - </p> - </section> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js index 419d174f3ff..bdfd4d9667c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js @@ -1,5 +1,5 @@ /* global Flash */ - +import statusIcon from '../mr_widget_status_icon'; import MRWidgetAuthor from '../../components/mr_widget_author'; import eventHub from '../../event_hub'; @@ -11,6 +11,7 @@ export default { }, components: { 'mr-widget-author': MRWidgetAuthor, + statusIcon, }, data() { return { @@ -61,56 +62,56 @@ export default { }, }, template: ` - <div class="mr-widget-body"> - <h4> - Set by - <mr-widget-author :author="mr.setToMWPSBy" /> - to be merged automatically when the pipeline succeeds. - <a - v-if="mr.canCancelAutomaticMerge" - @click.prevent="cancelAutomaticMerge" - :disabled="isCancellingAutoMerge" - role="button" - href="#" - class="btn btn-xs btn-default js-cancel-auto-merge"> - <i - v-if="isCancellingAutoMerge" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - Cancel automatic merge - </a> - </h4> - <section class="mr-info-list"> - <div class="legend"></div> - <p>The changes will be merged into - <a - :href="mr.targetBranchPath" - class="label-branch"> - {{mr.targetBranch}} - </a>. - </p> - <p v-if="mr.shouldRemoveSourceBranch"> - The source branch will be removed. - </p> - <p - v-else - class="with-button"> - The source branch will not be removed. + <div class="mr-widget-body media"> + <status-icon status="success" /> + <div class="media-body"> + <h4> + Set by + <mr-widget-author :author="mr.setToMWPSBy" /> + to be merged automatically when the pipeline succeeds <a - v-if="canRemoveSourceBranch" - :disabled="isRemovingSourceBranch" - @click.prevent="removeSourceBranch" + v-if="mr.canCancelAutomaticMerge" + @click.prevent="cancelAutomaticMerge" + :disabled="isCancellingAutoMerge" role="button" - class="btn btn-xs btn-default js-remove-source-branch" - href="#"> + href="#" + class="btn btn-xs btn-default js-cancel-auto-merge"> <i - v-if="isRemovingSourceBranch" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - Remove source branch + v-if="isCancellingAutoMerge" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + Cancel automatic merge </a> - </p> - </section> + </h4> + <section class="mr-info-list"> + <p>The changes will be merged into + <a + :href="mr.targetBranchPath" + class="label-branch"> + {{mr.targetBranch}} + </a> + </p> + <p v-if="mr.shouldRemoveSourceBranch"> + The source branch will be removed + </p> + <p v-else> + The source branch will not be removed + <a + v-if="canRemoveSourceBranch" + :disabled="isRemovingSourceBranch" + @click.prevent="removeSourceBranch" + role="button" + class="btn btn-xs btn-default js-remove-source-branch" + href="#"> + <i + v-if="isRemovingSourceBranch" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + Remove source branch + </a> + </p> + </section> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js index c7d32d18141..e452260a4d0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js @@ -1,6 +1,9 @@ /* global Flash */ import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; +import tooltip from '../../../vue_shared/directives/tooltip'; +import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; +import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; export default { @@ -9,14 +12,19 @@ export default { mr: { type: Object, required: true }, service: { type: Object, required: true }, }, - components: { - 'mr-widget-author-and-time': mrWidgetAuthorTime, - }, data() { return { isMakingRequest: false, }; }, + directives: { + tooltip, + }, + components: { + 'mr-widget-author-and-time': mrWidgetAuthorTime, + loadingIcon, + statusIcon, + }, computed: { shouldShowRemoveSourceBranch() { const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr; @@ -55,75 +63,77 @@ export default { }, }, template: ` - <div class="mr-widget-body"> - <mr-widget-author-and-time - actionText="Merged by" - :author="mr.mergedBy" - :dateTitle="mr.updatedAt" - :dateReadable="mr.mergedAt" /> - <section class="mr-info-list"> - <div class="legend"></div> - <p> - The changes were merged into - <span class="label-branch"> - <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> - </span> - </p> - <p v-if="mr.sourceBranchRemoved">The source branch has been removed.</p> - <p v-if="shouldShowRemoveSourceBranch"> - You can remove source branch now. - <button - @click="removeSourceBranch" - :class="{ disabled: isMakingRequest }" - type="button" - class="btn btn-xs btn-default js-remove-branch-button"> - Remove Source Branch - </button> - </p> - <p v-if="shouldShowSourceBranchRemoving"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - The source branch is being removed. - </p> - </section> - <div - v-if="shouldShowMergedButtons" - class="merged-buttons clearfix"> - <a - v-if="mr.canRevertInCurrentMR" - class="btn btn-close btn-sm has-tooltip" - href="#modal-revert-commit" - data-toggle="modal" - data-container="body" - title="Revert this merge request in a new merge request"> - Revert - </a> - <a - v-else-if="mr.revertInForkPath" - class="btn btn-close btn-sm has-tooltip" - data-method="post" - :href="mr.revertInForkPath" - title="Revert this merge request in a new merge request"> - Revert - </a> - <a - v-if="mr.canCherryPickInCurrentMR" - class="btn btn-default btn-sm has-tooltip" - href="#modal-cherry-pick-commit" - data-toggle="modal" - data-container="body" - title="Cherry-pick this merge request in a new merge request"> - Cherry-pick - </a> - <a - v-else-if="mr.cherryPickInForkPath" - class="btn btn-default btn-sm has-tooltip" - data-method="post" - :href="mr.cherryPickInForkPath" - title="Cherry-pick this merge request in a new merge request"> - Cherry-pick - </a> + <div class="mr-widget-body media"> + <status-icon status="success" /> + <div class="media-body"> + <div class="space-children"> + <mr-widget-author-and-time + actionText="Merged by" + :author="mr.mergedBy" + :dateTitle="mr.updatedAt" + :dateReadable="mr.mergedAt" /> + <a + v-if="mr.canRevertInCurrentMR" + v-tooltip + class="btn btn-close btn-xs" + href="#modal-revert-commit" + data-toggle="modal" + data-container="body" + title="Revert this merge request in a new merge request"> + Revert + </a> + <a + v-else-if="mr.revertInForkPath" + v-tooltip + class="btn btn-close btn-xs" + data-method="post" + :href="mr.revertInForkPath" + title="Revert this merge request in a new merge request"> + Revert + </a> + <a + v-if="mr.canCherryPickInCurrentMR" + v-tooltip + class="btn btn-default btn-xs" + href="#modal-cherry-pick-commit" + data-toggle="modal" + data-container="body" + title="Cherry-pick this merge request in a new merge request"> + Cherry-pick + </a> + <a + v-else-if="mr.cherryPickInForkPath" + v-tooltip + class="btn btn-default btn-xs" + data-method="post" + :href="mr.cherryPickInForkPath" + title="Cherry-pick this merge request in a new merge request"> + Cherry-pick + </a> + </div> + <section class="mr-info-list"> + <p> + The changes were merged into + <span class="label-branch"> + <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> + </span> + </p> + <p v-if="mr.sourceBranchRemoved">The source branch has been removed</p> + <p v-if="shouldShowRemoveSourceBranch" class="space-children"> + <span>You can remove source branch now</span> + <button + @click="removeSourceBranch" + :disabled="isMakingRequest" + type="button" + class="btn btn-xs btn-default js-remove-branch-button"> + Remove Source Branch + </button> + </p> + <p v-if="shouldShowSourceBranchRemoving"> + <loading-icon inline /> + <span>The source branch is being removed</span> + </p> + </section> </div> </div> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js new file mode 100644 index 00000000000..f6d1a4feeb2 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js @@ -0,0 +1,29 @@ +import statusIcon from '../mr_widget_status_icon'; + +export default { + name: 'MRWidgetMerging', + props: { + mr: { type: Object, required: true }, + }, + components: { + statusIcon, + }, + template: ` + <div class="mr-widget-body mr-state-locked media"> + <status-icon status="loading" /> + <div class="media-body"> + <h4> + This merge request is in the process of being merged + </h4> + <section class="mr-info-list"> + <p> + The changes will be merged into + <span class="label-branch"> + <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> + </span> + </p> + </section> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js index 328382485f6..9f0a359d01a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js @@ -1,3 +1,5 @@ +import statusIcon from '../mr_widget_status_icon'; +import tooltip from '../../../vue_shared/directives/tooltip'; import mrWidgetMergeHelp from '../../components/mr_widget_merge_help'; export default { @@ -5,30 +7,37 @@ export default { props: { mr: { type: Object, required: true }, }, + directives: { + tooltip, + }, components: { 'mr-widget-merge-help': mrWidgetMergeHelp, + statusIcon, }, computed: { missingBranchName() { return this.mr.sourceBranchRemoved ? 'source' : 'target'; }, + message() { + return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`; + }, }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold js-branch-text"> - <span class="capitalize"> - {{missingBranchName}} - </span> branch does not exist. - Please restore the {{missingBranchName}} branch or use a different {{missingBranchName}} branch. - </span> - <mr-widget-merge-help - :missing-branch="missingBranchName" /> + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold js-branch-text"> + <span class="capitalize"> + {{missingBranchName}} + </span> branch does not exist. + Please restore it or use a different {{missingBranchName}} branch + <i + v-tooltip + class="fa fa-question-circle" + :title="message" + :aria-label="message"></i> + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js index 07169b349be..797511d4e3a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js @@ -1,17 +1,19 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetNotAllowed', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - Ready to be merged automatically. - Ask someone with write access to this repository to merge this request. - </span> + <div class="mr-widget-body media"> + <status-icon status="success" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + Ready to be merged automatically. + Ask someone with write access to this repository to merge this request + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js index 375a382615a..ebfd6765934 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js @@ -12,7 +12,7 @@ export default { return { emptyStateSVG }; }, template: ` - <div class="mr-widget-body empty-state"> + <div class="mr-widget-body mr-widget-empty-state"> <div class="row"> <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center"> <span v-html="emptyStateSVG"></span> @@ -29,12 +29,14 @@ export default { Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch. </p> - <a - v-if="mr.newBlobPath" - :href="mr.newBlobPath" - class="btn btn-inverted btn-save"> - Create file - </a> + <div> + <a + v-if="mr.newBlobPath" + :href="mr.newBlobPath" + class="btn btn-inverted btn-save"> + Create file + </a> + </div> </div> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js index 31c53b679ed..167a0d4613a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js @@ -1,16 +1,18 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetPipelineBlocked', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - Pipeline blocked. The pipeline for this merge request requires a manual action to proceed. - </span> + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + Pipeline blocked. The pipeline for this merge request requires a manual action to proceed + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js index 002820123ca..c5be9a0530a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js @@ -1,16 +1,18 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetPipelineBlocked', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - class="btn btn-success btn-small" - disabled="true" - type="button"> - Merge - </button> - <span class="bold"> - The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure. - </span> + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index fcd4fdaf09f..65187754009 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -1,8 +1,8 @@ /* global Flash */ - import successSvg from 'icons/_icon_status_success.svg'; import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; +import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; export default { @@ -25,6 +25,9 @@ export default { warningSvg, }; }, + components: { + statusIcon, + }, computed: { commitMessageLinkTitle() { const withDesc = 'Include description in commit message'; @@ -196,84 +199,98 @@ export default { }, }, template: ` - <div class="mr-widget-body"> - <span class="btn-group"> - <button - @click="handleMergeButtonClick()" - :disabled="isMergeButtonDisabled" - :class="mergeButtonClass" - type="button"> - <i - v-if="isMakingRequest" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - {{mergeButtonText}} - </button> - <button - v-if="shouldShowMergeOptionsDropdown" - :disabled="isMergeButtonDisabled" - type="button" - class="btn btn-small btn-info dropdown-toggle" - data-toggle="dropdown"> - <i - class="fa fa-caret-down" - aria-hidden="true" /> - <span class="sr-only"> - Select merge moment + <div class="mr-widget-body media"> + <status-icon status="success" /> + <div class="media-body"> + <div class="media space-children"> + <span class="btn-group"> + <button + @click="handleMergeButtonClick()" + :disabled="isMergeButtonDisabled" + :class="mergeButtonClass" + type="button"> + <i + v-if="isMakingRequest" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + {{mergeButtonText}} + </button> + <button + v-if="shouldShowMergeOptionsDropdown" + :disabled="isMergeButtonDisabled" + type="button" + class="btn btn-small btn-info dropdown-toggle js-merge-moment" + data-toggle="dropdown" + aria-label="Select merge moment"> + <i + class="fa fa-chevron-down" + aria-hidden="true" /> + </button> + <ul + v-if="shouldShowMergeOptionsDropdown" + class="dropdown-menu dropdown-menu-right" + role="menu"> + <li> + <a + @click.prevent="handleMergeButtonClick(true)" + class="merge_when_pipeline_succeeds" + href="#"> + <span class="media"> + <span + v-html="successSvg" + class="merge-opt-icon" + aria-hidden="true"></span> + <span class="media-body merge-opt-title">Merge when pipeline succeeds</span> + </span> + </a> + </li> + <li> + <a + @click.prevent="handleMergeButtonClick(false, true)" + class="accept-merge-request" + href="#"> + <span class="media"> + <span + v-html="warningSvg" + class="merge-opt-icon" + aria-hidden="true"></span> + <span class="media-body merge-opt-title">Merge immediately</span> + </span> + </a> + </li> + </ul> </span> - </button> - <ul - v-if="shouldShowMergeOptionsDropdown" - class="dropdown-menu dropdown-menu-right" - role="menu"> - <li> - <a - @click.prevent="handleMergeButtonClick(true)" - class="merge_when_pipeline_succeeds" - href="#"> - <span - v-html="successSvg" - class="merge-opt-icon" - aria-hidden="true"></span> - <span class="merge-opt-title">Merge when pipeline succeeds</span> - </a> - </li> - <li> - <a - @click.prevent="handleMergeButtonClick(false, true)" - class="accept-merge-request" - href="#"> - <span - v-html="warningSvg" - class="merge-opt-icon" - aria-hidden="true"></span> - <span class="merge-opt-title">Merge immediately</span> - </a> - </li> - </ul> - </span> - <template v-if="isMergeAllowed()"> - <label class="spacing"> - <input - id="remove-source-branch-input" - v-model="removeSourceBranch" - :disabled="isRemoveSourceBranchButtonDisabled" - type="checkbox"/> Remove source branch - </label> + <div class="media-body space-children"> + <template v-if="isMergeAllowed()"> + <label> + <input + id="remove-source-branch-input" + v-model="removeSourceBranch" + :disabled="isRemoveSourceBranchButtonDisabled" + type="checkbox"/> Remove source branch + </label> - <!-- Placeholder for EE extension of this component --> - <squash-before-merge - v-if="shouldShowSquashBeforeMerge" - :mr="mr" - :is-merge-button-disabled="isMergeButtonDisabled" /> + <!-- Placeholder for EE extension of this component --> + <squash-before-merge + v-if="shouldShowSquashBeforeMerge" + :mr="mr" + :is-merge-button-disabled="isMergeButtonDisabled" /> - <button - @click="toggleCommitMessageEditor" - :disabled="isMergeButtonDisabled" - class="btn btn-default btn-xs" - type="button"> - Modify commit message - </button> + <button + @click="toggleCommitMessageEditor" + :disabled="isMergeButtonDisabled" + class="btn btn-default btn-xs" + type="button"> + Modify commit message + </button> + </template> + <template v-else> + <span class="bold"> + The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure + </span> + </template> + </div> + </div> <div v-if="showCommitMessageEditor" class="prepend-top-default commit-message-editor"> @@ -293,7 +310,7 @@ export default { rows="14" name="Commit message"></textarea> </div> - <p class="hint">Try to keep the first line under 52 characters and the others under 72.</p> + <p class="hint">Try to keep the first line under 52 characters and the others under 72</p> <div class="hint"> <a @click.prevent="updateCommitMessage" @@ -302,12 +319,7 @@ export default { </div> </div> </div> - </template> - <template v-else> - <span class="bold"> - The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure. - </span> - </template> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js index 79f8ef408e6..89f38e5bd2a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js @@ -1,16 +1,18 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetSHAMismatch', + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - The source branch HEAD has recently changed. Please reload the page and review the changes before merging. - </span> + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + The source branch HEAD has recently changed. Please reload the page and review the changes before merging + </span> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js index f4ab2d9fa58..d762ca6e640 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js @@ -1,27 +1,27 @@ +import statusIcon from '../mr_widget_status_icon'; + export default { name: 'MRWidgetUnresolvedDiscussions', props: { mr: { type: Object, required: true }, }, + components: { + statusIcon, + }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge - </button> - <span class="bold"> - There are unresolved discussions. Please resolve these discussions - <span v-if="mr.canCreateIssue">or</span> - <span v-else>.</span> - </span> - <a - v-if="mr.createIssueToResolveDiscussionsPath" - :href="mr.createIssueToResolveDiscussionsPath" - class="btn btn-default btn-xs js-create-issue"> - Create an issue to resolve them later - </a> + <div class="mr-widget-body media"> + <status-icon status="failed" showDisabledButton /> + <div class="media-body space-children"> + <span class="bold"> + There are unresolved discussions. Please resolve these discussions + </span> + <a + v-if="mr.createIssueToResolveDiscussionsPath" + :href="mr.createIssueToResolveDiscussionsPath" + class="btn btn-default btn-xs js-create-issue"> + Create an issue to resolve them later + </a> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js index cb02ffe93bd..b11a06899cf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js @@ -1,4 +1,6 @@ /* global Flash */ +import statusIcon from '../mr_widget_status_icon'; +import tooltip from '../../../vue_shared/directives/tooltip'; import eventHub from '../../event_hub'; export default { @@ -7,11 +9,17 @@ export default { mr: { type: Object, required: true }, service: { type: Object, required: true }, }, + directives: { + tooltip, + }, data() { return { isMakingRequest: false, }; }, + components: { + statusIcon, + }, methods: { removeWIP() { this.isMakingRequest = true; @@ -29,20 +37,20 @@ export default { }, }, template: ` - <div class="mr-widget-body"> - <button - type="button" - class="btn btn-success btn-small" - disabled="true"> - Merge</button> - <span class="bold"> - This merge request is currently Work In Progress and therefore unable to merge - </span> - <template v-if="mr.removeWIPPath"> - <i - class="fa fa-question-circle has-tooltip" - title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged." /> + <div class="mr-widget-body media"> + <status-icon status="failed" :showDisabledButton="Boolean(mr.removeWIPPath)" /> + <div class="media-body space-children"> + <span class="bold"> + This is a Work in Progress + <i + v-tooltip + class="fa fa-question-circle" + title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged" + aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"> + </i> + </span> <button + v-if="mr.removeWIPPath" @click="removeWIP" :disabled="isMakingRequest" type="button" @@ -53,7 +61,7 @@ export default { aria-hidden="true" /> Resolve WIP status </button> - </template> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index fe5e1bbb55c..49340c232c8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -1,7 +1,7 @@ /** * This file is the centerpiece of an attempt to reduce potential conflicts * between the CE and EE versions of the MR widget. EE additions to the MR widget should - * be contained in the ./vue_merge_request_widget/ee directory, and should **extend** + * be contained in the ee/vue_merge_request_widget directory, and should **extend** * rather than mutate CE MR Widget code. * * This file should be the only source of conflicts between EE and CE. EE-only components should @@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li export { default as MergedState } from './components/states/mr_widget_merged'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge'; export { default as ClosedState } from './components/states/mr_widget_closed'; -export { default as LockedState } from './components/states/mr_widget_locked'; +export { default as MergingState } from './components/states/mr_widget_merging'; export { default as WipState } from './components/states/mr_widget_wip'; export { default as ArchivedState } from './components/states/mr_widget_archived'; export { default as ConflictsState } from './components/states/mr_widget_conflicts'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 2339a00ddd0..0042c48816f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -8,7 +8,7 @@ import { WidgetRelatedLinks, MergedState, ClosedState, - LockedState, + MergingState, WipState, ArchivedState, ConflictsState, @@ -35,8 +35,14 @@ import { export default { el: '#js-vue-mr-widget', name: 'MRWidget', + props: { + mrData: { + type: Object, + required: false, + }, + }, data() { - const store = new MRWidgetStore(gl.mrWidgetData); + const store = new MRWidgetStore(this.mrData || window.gl.mrWidgetData); const service = this.createService(store); return { mr: store, @@ -206,7 +212,7 @@ export default { 'mr-widget-related-links': WidgetRelatedLinks, 'mr-widget-merged': MergedState, 'mr-widget-closed': ClosedState, - 'mr-widget-locked': LockedState, + 'mr-widget-merging': MergingState, 'mr-widget-failed-to-merge': FailedToMerge, 'mr-widget-wip': WipState, 'mr-widget-archived': ArchivedState, @@ -234,14 +240,21 @@ export default { v-if="shouldRenderDeployments" :mr="mr" :service="service" /> - <component - :is="componentName" - :mr="mr" - :service="service" /> - <mr-widget-related-links - v-if="shouldRenderRelatedLinks" - :related-links="mr.relatedLinks" /> - <mr-widget-merge-help v-if="shouldRenderMergeHelp" /> + <div class="mr-widget-section"> + <component + :is="componentName" + :mr="mr" + :service="service" /> + <mr-widget-related-links + v-if="shouldRenderRelatedLinks" + :state="mr.state" + :related-links="mr.relatedLinks" /> + </div> + <div + class="mr-widget-footer" + v-if="shouldRenderMergeHelp"> + <mr-widget-merge-help /> + </div> </div> `, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index fddafb0ddfa..fbea764b739 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -73,6 +73,7 @@ export default class MergeRequestStore { this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path; this.hasSHAChanged = this.sha !== data.diff_head_sha; this.canBeMerged = data.can_be_merged || false; + this.mergeOngoing = data.merge_ongoing; // Cherry-pick and Revert actions related this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; @@ -94,6 +95,11 @@ export default class MergeRequestStore { } setState(data) { + if (this.mergeOngoing) { + this.state = 'merging'; + return; + } + if (this.isOpen) { this.state = getStateKey.call(this, data); } else { @@ -104,9 +110,6 @@ export default class MergeRequestStore { case 'closed': this.state = 'closed'; break; - case 'locked': - this.state = 'locked'; - break; default: this.state = null; } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 605dd3a1ff4..9074a064a6d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -1,7 +1,7 @@ const stateToComponentMap = { merged: 'mr-widget-merged', closed: 'mr-widget-closed', - locked: 'mr-widget-locked', + merging: 'mr-widget-merging', conflicts: 'mr-widget-conflicts', missingBranch: 'mr-widget-missing-branch', workInProgress: 'mr-widget-wip', @@ -20,7 +20,7 @@ const stateToComponentMap = { }; const statesToShowHelpWidget = [ - 'locked', + 'merging', 'conflicts', 'workInProgress', 'readyToMerge', diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue new file mode 100644 index 00000000000..7d339c0e753 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue @@ -0,0 +1,67 @@ +<script> +const PopupDialog = { + name: 'popup-dialog', + + props: { + open: Boolean, + title: String, + body: String, + kind: { + type: String, + default: 'primary', + }, + closeButtonLabel: { + type: String, + default: 'Cancel', + }, + primaryButtonLabel: { + type: String, + default: 'Save changes', + }, + }, + + computed: { + typeOfClass() { + const className = `btn-${this.kind}`; + const returnObj = {}; + returnObj[className] = true; + return returnObj; + }, + }, + + methods: { + close() { + this.$emit('toggle', false); + }, + + yesClick() { + this.$emit('submit', true); + }, + + noClick() { + this.$emit('submit', false); + }, + }, +}; + +export default PopupDialog; +</script> +<template> +<div class="modal popup-dialog" tabindex="-1" v-show="open" role="dialog"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" @click="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title">{{this.title}}</h4> + </div> + <div class="modal-body"> + <p>{{this.body}}</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal" @click="noClick">{{closeButtonLabel}}</button> + <button type="button" class="btn" :class="typeOfClass" @click="yesClick">{{primaryButtonLabel}}</button> + </div> + </div> + </div> +</div> +</template> diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js index 00676bcb0b3..51ed2b4fd15 100644 --- a/app/assets/javascripts/wikis.js +++ b/app/assets/javascripts/wikis.js @@ -1,6 +1,5 @@ /* global Breakpoints */ -import 'vendor/jquery.nicescroll'; import './breakpoints'; export default class Wikis { @@ -8,7 +7,6 @@ export default class Wikis { this.bp = Breakpoints.get(); this.sidebarEl = document.querySelector('.js-wiki-sidebar'); this.sidebarExpanded = false; - $(this.sidebarEl).niceScroll(); const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle'); for (let i = 0; i < sidebarToggles.length; i += 1) { diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 6ce331a9129..b2b3297e880 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -26,6 +26,7 @@ @import "framework/lists"; @import "framework/logo"; @import "framework/markdown_area"; +@import "framework/media_object"; @import "framework/mobile"; @import "framework/modal"; @import "framework/nav"; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index cb41df8a88d..486d88efbc5 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -100,6 +100,8 @@ margin: 0; align-self: center; } + + &.s40 { min-width: 40px; min-height: 40px; } } .avatar-counter { diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 0ac095f7d8f..0ded4a3b423 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -45,6 +45,7 @@ margin-top: -23px; float: right; font-size: 12px; + direction: ltr; } .pika-single.gitlab-theme { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 5e374360359..293aa194528 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -372,6 +372,10 @@ table { background: $gl-success !important; } +.dz-message { + margin: 0; +} + .space-right { margin-right: 10px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 3f934403147..02e0ba74158 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -574,6 +574,7 @@ .dropdown-input-field, .default-dropdown-input { + display: block; width: 100%; min-height: 30px; padding: 0 7px; @@ -722,3 +723,57 @@ @include set-invisible; overflow: hidden; } + +// TODO: change global style and remove mixin +@mixin new-style-dropdown { + .dropdown-menu, + .dropdown-menu-nav { + .divider { + margin: 6px 0; + } + + li { + padding: 0 1px; + + &.dropdown-header { + padding: 8px 16px; + } + + a { + border-radius: 0; + padding: 8px 16px; + + &.is-focused, + &:hover, + &:active, + &:focus { + background-color: $gray-darker; + } + + &.is-active { + font-weight: inherit; + + &::before { + top: 16px; + } + } + } + } + + &.dropdown-menu-selectable { + li { + a { + padding: 8px 40px; + + &.is-active::before { + left: 16px; + } + } + } + } + } + + .dropdown-menu-align-right { + margin-top: 2px; + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 1c4238bc564..b677882eba4 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -4,6 +4,8 @@ */ header { + @include new-style-dropdown; + transition: padding $sidebar-transition-duration; &.navbar-empty { @@ -24,7 +26,7 @@ header { &.navbar-gitlab { padding: 0 16px; - z-index: 400; + z-index: 1000; margin-bottom: 0; min-height: $header-height; background-color: $gray-light; @@ -313,25 +315,6 @@ header { .impersonation i { color: $red-500; } - - // TODO: fallback to global style - .dropdown-menu, - .dropdown-menu-nav { - li { - padding: 0 1px; - - a { - border-radius: 0; - padding: 8px 16px; - - &:hover, - &:active, - &:focus { - background-color: $gray-darker; - } - } - } - } } .with-performance-bar header.navbar-gitlab { @@ -342,9 +325,9 @@ header { li { .badge { position: inherit; - top: -3px; + top: -8px; font-weight: normal; - margin-left: -12px; + margin-left: -11px; font-size: 11px; color: $white-light; padding: 1px 5px 2px; diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 67c3287ed74..bd0367f86dd 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -109,18 +109,20 @@ body { } } - -/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch, -which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side -effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children -of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */ - -.navbar, -.page-gutter, -.page-with-sidebar { - -webkit-overflow-scrolling: auto; +.page-with-sidebar > .content-wrapper { + min-height: calc(100vh - #{$header-height}); } .with-performance-bar .page-with-sidebar { margin-top: $header-height + $performance-bar-height; } + +[v-cloak] { + display: none; +} + +.vertical-center { + min-height: 100vh; + display: flex; + align-items: center; +} diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 868e65a8f46..ab754f4a492 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -369,6 +369,10 @@ ul.indent-list { background-color: $row-hover; cursor: pointer; } + + .avatar-container > a { + width: 100%; + } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index a2de4598167..fcd4c72b430 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -185,3 +185,28 @@ text-overflow: ellipsis; } } + +// TODO: fallback to global style +.atwho-view { + .atwho-view-ul { + padding: 8px 1px; + + li { + padding: 8px 16px; + border: 0; + + &.cur { + background-color: $gray-darker; + color: $gl-text-color; + + small { + color: inherit; + } + } + + strong { + color: $gl-text-color; + } + } + } +} diff --git a/app/assets/stylesheets/framework/media_object.scss b/app/assets/stylesheets/framework/media_object.scss new file mode 100644 index 00000000000..b573052c14a --- /dev/null +++ b/app/assets/stylesheets/framework/media_object.scss @@ -0,0 +1,8 @@ +.media { + display: flex; + align-items: flex-start; +} + +.media-body { + flex: 1; +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 88e7ba117d5..d386ac5ba9c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -251,7 +251,6 @@ // Applies on /dashboard/issues .project-item-select-holder { - display: block; margin: 0; } } @@ -283,6 +282,31 @@ } } +.project-item-select-holder.btn-group { + display: flex; + max-width: 350px; + overflow: hidden; + + @media(max-width: $screen-xs-max) { + width: 100%; + max-width: none; + } + + .new-project-item-link { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .new-project-item-select-button { + width: 32px; + } +} + +.new-project-item-select-button .fa-caret-down { + margin-left: 2px; +} + .layout-nav { width: 100%; background: $gray-light; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 09b60ad1676..40e8a928e6e 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -78,15 +78,12 @@ .right-sidebar { border-left: 1px solid $border-color; + height: calc(100% - #{$header-height}); &.affix { position: fixed; top: $header-height; } - - &:not(.affix-top) { - min-height: 100%; - } } .with-performance-bar .right-sidebar.affix { diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index b666223b120..4c35e3a9c3c 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -162,3 +162,5 @@ $pre-color: $gl-text-color !default; $pre-border-color: $border-color; $table-bg-accent: $gray-light; + +$zindex-popover: 900; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0df6f24bfe6..3c109a5a929 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -88,6 +88,7 @@ $indigo-950: #1a1a40; $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); +$almost-black: #242424; $border-white-light: darken($white-light, $darken-border-factor); $border-white-normal: darken($white-normal, $darken-border-factor); @@ -206,7 +207,6 @@ $general-hover-transition-curve: linear; $highlight-changes-color: rgb(235, 255, 232); $performance-bar-height: 35px; - /* * Common component specific colors */ @@ -316,6 +316,12 @@ $badge-bg: rgba(0, 0, 0, 0.07); $badge-color: $gl-text-color-secondary; /* + * Status icons + */ +$status-icon-size: 22px; +$status-icon-margin: $gl-btn-padding; + +/* * Award emoji */ $award-emoji-menu-shadow: rgba(0, 0, 0, .175); @@ -614,6 +620,13 @@ $color-average-score: $orange-400; $color-low-score: $red-400; /* +Repo editor +*/ +$repo-editor-grey: #f6f7f9; +$repo-editor-grey-darker: #e9ebee; +$repo-editor-linear-gradient: linear-gradient(to right, $repo-editor-grey 0%, $repo-editor-grey-darker, 20%, $repo-editor-grey 40%, $repo-editor-grey 100%); + +/* Performance Bar */ $perf-bar-text: #999; @@ -624,3 +637,11 @@ $perf-bar-bucket-bg: #111; $perf-bar-bucket-color: #ccc; $perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-to: rgba($black, .25); + + +/* +Project Templates Icons +*/ +$rails: #c00; +$node: #353535; +$java: #70ad51; diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 1c4a84de7ec..795ee91af8b 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -312,6 +312,10 @@ header.navbar-gitlab-new { // TODO: fallback to global style .dropdown-menu { + .divider { + margin: 6px 0; + } + li { padding: 0 1px; diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 54f3e8d882c..4367b8c1a15 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -8,20 +8,25 @@ $active-color: $indigo-700; $active-hover-background: $active-background; $active-hover-color: $gl-text-color; $inactive-badge-background: rgba(0, 0, 0, .08); -$hover-background: $indigo-700; -$hover-color: $white-light; +$hover-background: $white-light; +$hover-color: $gl-text-color; $inactive-color: $gl-text-color-secondary; $new-sidebar-width: 220px; +$new-sidebar-collapsed-width: 50px; .page-with-new-sidebar { - @media (min-width: $screen-sm-min) { + @media (min-width: $screen-md-min) { + padding-left: $new-sidebar-collapsed-width; + } + + @media (min-width: $screen-lg-min) { padding-left: $new-sidebar-width; } // Override position: absolute .right-sidebar { position: fixed; - height: 100%; + height: calc(100% - #{$header-height}); } .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { @@ -29,8 +34,15 @@ $new-sidebar-width: 220px; } } +.page-with-icon-sidebar { + @media (min-width: $screen-sm-min) { + padding-left: $new-sidebar-collapsed-width; + } +} + .context-header { position: relative; + margin-right: 2px; a { border-bottom: 1px solid $border-color; @@ -39,26 +51,16 @@ $new-sidebar-width: 220px; align-items: center; padding: 10px 16px 10px 10px; color: $gl-text-color; + } - @media (max-width: $screen-xs-max) { - padding-right: 30px; - } - - &:hover { - background-color: $hover-background; - color: $hover-color; - border-color: $hover-background; - - .avatar-container { - border-color: transparent; - } - - .settings-avatar { - background-color: $indigo-500; + &:hover, + a:hover { + background-color: $hover-background; + color: $hover-color; - i { - color: $hover-color; - } + .settings-avatar { + i { + color: $hover-color; } } } @@ -73,32 +75,6 @@ $new-sidebar-width: 220px; overflow: hidden; text-overflow: ellipsis; } - - - &:hover { - .close-nav-button { - color: $white-light; - } - } - - .close-nav-button { - display: none; - position: absolute; - top: 0; - right: 0; - height: 100%; - background-color: transparent; - border: 0; - padding: 0 10px; - - @media (max-width: $screen-xs-max) { - display: block; - } - - &:hover { - color: $gl-text-color; - } - } } .settings-avatar { @@ -125,6 +101,19 @@ $new-sidebar-width: 220px; background-color: $gray-normal; box-shadow: inset -2px 0 0 $border-color; + &.sidebar-icons-only { + width: $new-sidebar-collapsed-width; + + .badge, + .project-title { + display: none; + } + + .nav-item-name { + opacity: 0; + } + } + &.nav-sidebar-expanded { left: 0; } @@ -143,10 +132,19 @@ $new-sidebar-width: 220px; white-space: nowrap; a { - display: block; + display: flex; + align-items: center; padding: 12px 16px; color: $inactive-color; } + + svg { + fill: $inactive-color; + } + } + + .nav-item-name { + flex: 1; } li.active { @@ -156,11 +154,25 @@ $new-sidebar-width: 220px; color: $active-color; font-weight: 700; } + + svg { + fill: $active-color; + } } @media (max-width: $screen-xs-max) { left: (-$new-sidebar-width); } + + .nav-icon-container { + display: flex; + margin-right: 8px; + + svg { + height: 16px; + width: 16px; + } + } } .with-performance-bar .nav-sidebar { @@ -173,7 +185,7 @@ $new-sidebar-width: 220px; > li { a { - padding: 8px 16px 8px 24px; + padding: 8px 16px 8px 40px; &:hover, &:focus { @@ -196,9 +208,97 @@ $new-sidebar-width: 220px; } .sidebar-top-level-items { + margin-bottom: 60px; + > li { + > a { + @media (min-width: $screen-sm-min) { + margin-right: 2px; + } + + &:hover { + color: $gl-text-color; + + svg { + fill: $gl-text-color; + } + } + } + + &.is-showing-fly-out { + > a { + margin-right: 2px; + } + + .sidebar-sub-level-items { + @media (min-width: $screen-sm-min) { + position: fixed; + top: 0; + left: $new-sidebar-width; + min-width: 150px; + margin-top: -1px; + padding: 8px 1px; + background-color: $white-light; + box-shadow: 2px 1px 3px $dropdown-shadow-color; + border: 1px solid $gray-darker; + border-left: 0; + border-radius: 0 3px 3px 0; + + &::before { + content: ""; + position: absolute; + top: -30px; + bottom: -30px; + left: 0; + right: -30px; + z-index: -1; + } + + &::after { + content: ""; + position: absolute; + top: 44px; + left: -30px; + right: 35px; + bottom: 0; + height: 100%; + max-height: 150px; + z-index: -1; + transform: skew(33deg); + } + + &.is-above { + margin-top: 1px; + + &::after { + top: auto; + bottom: 44px; + transform: skew(-30deg); + } + } + + > .active { + box-shadow: none; + + > a { + background-color: transparent; + } + } + + a { + padding: 8px 16px; + color: $gl-text-color; + + &:hover, + &:focus { + background-color: $gray-darker; + } + } + } + } + } + .badge { - float: right; background-color: $inactive-badge-background; color: $inactive-color; } @@ -206,6 +306,11 @@ $new-sidebar-width: 220px; &.active { background: $active-background; + > a { + margin-left: 4px; + padding-left: 12px; + } + .badge { color: $active-color; font-weight: 600; @@ -216,16 +321,101 @@ $new-sidebar-width: 220px; } } - > a:hover { - background-color: $hover-background; - color: $hover-color; + &:not(.active):hover > a, + > a:hover, + &.is-over > a { + background-color: $white-light; + } + } +} - .badge { - background-color: $indigo-500; - color: $hover-color; + +// Collapsed nav + +.toggle-sidebar-button, +.close-nav-button { + width: $new-sidebar-width - 2px; + position: fixed; + bottom: 0; + padding: 16px; + background-color: $gray-normal; + border: 0; + border-top: 2px solid $border-color; + color: $gl-text-color-secondary; + display: flex; + align-items: center; + + i { + font-size: 20px; + margin-right: 8px; + } + + .fa-angle-double-right { + display: none; + } + + &:hover { + background-color: $border-color; + color: $gl-text-color; + } +} + +.toggle-sidebar-button { + @media (max-width: $screen-xs-max) { + display: none; + } +} + + +.sidebar-icons-only { + .context-header { + height: 61px; + + a { + padding: 10px 4px; + } + } + + li a { + padding: 12px 15px; + } + + .sidebar-top-level-items > li { + &.active a { + padding-left: 12px; + } + + .sidebar-sub-level-items { + @media (min-width: $screen-sm-min) { + left: $new-sidebar-collapsed-width; + } + + &:not(.flyout-list) { + display: none; } } } + + .toggle-sidebar-button { + width: $new-sidebar-collapsed-width - 2px; + padding: 16px 18px; + + .collapse-text, + .fa-angle-double-left { + display: none; + } + + .fa-angle-double-right { + display: block; + } + } +} + + +// Mobile nav + +.close-nav-button { + display: none; } .toggle-mobile-nav { @@ -247,6 +437,12 @@ $new-sidebar-width: 220px; } } +@media (max-width: $screen-xs-max) { + .close-nav-button { + display: flex; + } +} + .mobile-overlay { display: none; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6039cda96d8..e5b467a2691 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -165,6 +165,7 @@ .board-title { padding-top: ($gl-padding - 3px); + padding-bottom: $gl-padding; } } } @@ -178,6 +179,7 @@ position: relative; margin: 0; padding: $gl-padding; + padding-bottom: ($gl-padding + 3px); font-size: 1em; border-bottom: 1px solid $border-color; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index acf3719e9d2..486424fb729 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -235,8 +235,18 @@ display: none; } + .sidebar-container { + width: calc(100% + 100px); + padding-right: 100px; + height: 100%; + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + .blocks-container { padding: 0 $gl-padding; + width: 289px; } .block { @@ -259,7 +269,15 @@ padding: 16px 0; } + .trigger-build-variables { + margin: 0; + overflow-x: auto; + -ms-overflow-style: scrollbar; + -webkit-overflow-scrolling: touch; + } + .trigger-build-variable { + font-weight: normal; color: $code-color; } @@ -311,9 +329,7 @@ } .dropdown-menu { - right: $gl-padding; - left: $gl-padding; - width: auto; + margin-top: -$gl-padding; } svg { @@ -328,6 +344,7 @@ border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; max-height: 300px; + width: 289px; overflow: auto; svg { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index eeb90759f10..6753eb08285 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -1,4 +1,6 @@ #cycle-analytics { + @include new-style-dropdown; + max-width: 1000px; margin: 24px auto 0; position: relative; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 398fd4444ea..da77346d8b2 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -395,12 +395,11 @@ background-color: transparent; border: 0; color: $gl-link-color; - transition: color 0.1s linear; + font-weight: 600; &:hover, &:focus { outline: none; - text-decoration: underline; color: $gl-link-hover-color; } } @@ -559,3 +558,68 @@ outline: 0; } } + +.diff-files-changed { + .commit-stat-summary { + @include new-style-dropdown; + z-index: -1; + + @media (min-width: $screen-sm-min) { + margin-left: -$gl-padding; + padding-left: $gl-padding; + background-color: $white-light; + } + } + + @media (min-width: $screen-sm-min) { + position: -webkit-sticky; + position: sticky; + top: 84px; + background-color: $white-light; + z-index: 190; + + + .files, + + .alert { + margin-top: 1px; + } + + &:not(.is-stuck) .diff-stats-additions-deletions-collapsed { + display: none; + } + + &.is-stuck { + padding-top: 0; + padding-bottom: 0; + border-bottom: 1px solid $white-dark; + transform: translateY(16px); + + .diff-stats-additions-deletions-expanded, + .inline-parallel-buttons { + display: none; + } + + + .files, + + .alert { + margin-top: 30px; + } + } + } +} + +.diff-file-changes { + width: 450px; + z-index: 150; + + @media (min-width: $screen-sm-min) { + left: $gl-padding; + } + + a { + padding-top: 8px; + padding-bottom: 8px; + } +} + +.diff-file-changes-path { + @include str-truncated(78%); +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6da14320914..d14b976374c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -5,13 +5,37 @@ margin-right: auto; } +.is-confidential { + color: $orange-600; + background-color: $orange-50; + border-radius: 3px; + padding: 5px; + margin: 0 3px 0 -4px; +} + +.is-not-confidential { + border-radius: 3px; + padding: 5px; + margin: 0 3px 0 -4px; +} + +.confidentiality { + .is-not-confidential { + margin: auto; + } + + .is-confidential { + margin: auto; + } +} + .limit-container-width { .detail-page-header, .page-content-header, .commit-box, .info-well, .commit-ci-menu, - .files-changed, + .files-changed-inner, .limited-header-width, .limited-width-notes { @extend .fixed-width-container; @@ -328,9 +352,17 @@ margin-bottom: 10px; color: $issuable-sidebar-color; + svg { + fill: $issuable-sidebar-color; + } + &:hover, &:hover .todo-undone { color: $gl-text-color; + + svg { + fill: $gl-text-color; + } } span { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 4693b2434c7..6bb013cca85 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -2,10 +2,35 @@ * MR -> show: Automerge widget * */ + +.space-children { + @include clearfix; + + > * { + float: left; + } + + > *:not(:last-child) { + margin-right: 10px; + } +} + .mr-state-widget { color: $gl-text-color; border: 1px solid $border-color; border-radius: 2px; + line-height: 28px; + + .mr-widget-heading, + .mr-widget-section, + .mr-widget-footer { + padding: $gl-padding; + border-top: solid 1px $border-color; + } + + .mr-widget-footer { + padding: 0; + } form { margin-bottom: 0; @@ -15,15 +40,35 @@ } } + label { + margin-bottom: 0; + } + + .btn { + font-size: $gl-font-size; + + &[disabled] { + opacity: 0.3; + } + + &.btn-xs { + line-height: 1; + padding: 5px 10px; + margin-top: 1px; + } + + &.dropdown-toggle { + .fa { + color: inherit; + } + } + } + .accept-merge-holder { .accept-action { display: inline-block; float: left; - .btn-success.dropdown-toggle .fa { - color: inherit; - } - .accept-merge-request { &.ci-pending, &.ci-running { @@ -84,77 +129,64 @@ .ci-widget { color: $gl-text-color; - display: -webkit-flex; display: flex; - -webkit-align-items: center; - align-items: center; - padding: $gl-padding-top $gl-padding 0; - - svg { - position: relative; - top: 1px; - overflow: visible; - } - - > span { - padding-right: 4px; - } @media (max-width: $screen-xs-max) { flex-wrap: wrap; } + } - .icon-link > .ci-status-icon > svg { - width: 22px; - height: 22px; - margin-right: 8px; - } + .mr-widget-icon { + font-size: 22px; + margin-right: $status-icon-margin; + } - .ci-error { - margin-right: $btn-side-margin; - } + .ci-status-icon svg { + width: $status-icon-size; + height: $status-icon-size; + margin: 3px 0; + position: relative; + overflow: visible; + display: block; } - .mr-widget-body, - .mr-widget-footer { - margin: 16px; + .mr-widget-body { + @include clearfix; + + &.media > *:first-child { + margin-right: 10px; + } } .mr-widget-pipeline-graph { - flex-shrink: 0; + padding: 0 4px; .dropdown-menu { - margin-top: 11px; z-index: 300; } .ci-action-icon-wrapper { line-height: 16px; } + } - @media (max-width: $screen-xs-max) { - order: 1; - margin-top: $gl-padding-top; - border-radius: 3px; - background-color: $white-light; - border: 1px solid $gray-darker; - width: 100%; - text-align: center; + .mini-pipeline-graph-dropdown-toggle { + vertical-align: top; + } - .dropdown-menu { - margin-left: -97.5px; - } + .mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item { + display: flex; + align-items: center; - .arrow-up::before, - .arrow-up::after, { - margin-left: 97.5px; - } + .ci-status-text, + .ci-status-icon { + top: 0; + margin-right: 10px; } } .normal { - color: $gl-text-color; - font-size: 15px; + line-height: 28px; } .capitalize { @@ -165,9 +197,8 @@ @extend .ref-name; color: $gl-text-color; - font-weight: bold; + font-weight: 600; overflow: hidden; - margin: 0 3px; word-break: break-all; &.label-truncated { @@ -189,52 +220,19 @@ } } - .js-deployment-link { - display: inline-block; - } - .mr-widget-help { - margin: $gl-padding; - color: $ci-skipped-color; - } - - .mr-info-list { - - &.mr-links { - margin-left: 28px; - } - - &.mr-memory-usage { - margin: 5px 0 10px 25px; - } - } - - .mr-widget-heading { - .btn-default.btn-xs { - margin-left: 5px; - } - } - - .mr-widget-body { - .btn { - font-size: 15px; - } - - .btn-group .btn { - padding: 5px 10px; - - &.dropdown-toggle { - padding: 5px 7px; - } - } + padding: 10px 16px 10px 48px; + font-style: italic; } .mr-widget-body { h4 { - font-weight: bold; - font-size: 15px; - margin: 5px 0; - color: $gl-text-color; + float: left; + font-weight: 600; + font-size: 14px; + line-height: inherit; + margin-top: 0; + margin-bottom: 0; &.has-conflicts .fa-exclamation-triangle { color: $gl-warning; @@ -255,18 +253,16 @@ } .spacing { - margin: 0 $gl-padding; + margin: 0 0 0 10px; } .bold { - font-weight: bold; - font-size: 15px; + font-weight: 600; color: $gl-gray-light; } .state-label { - font-size: 16px; - font-weight: bold; + font-weight: 600; padding-right: 10px; } @@ -274,16 +270,6 @@ color: $gl-danger; } - .mr-widget-help { - margin: $gl-padding 0; - } - - .with-button { - position: relative; - top: 6px; - margin-bottom: 24px; - } - .spacing, .bold { vertical-align: middle; @@ -294,15 +280,8 @@ padding: 5px; } - .merge-opt-icon, - .merge-opt-title { - display: inline-block; - float: left; - } - - .merge-opt-icon svg { - height: 15px; - width: 15px; + .merge-opt-icon { + line-height: 1.5; } .merge-opt-title { @@ -316,34 +295,15 @@ } } - .has-error-message + .has-custom-error { - margin-left: 0; - } - .has-custom-error { display: inline-block; - margin-left: 70px; - } - - .merge-error-text { - margin-left: 70px; } @media (max-width: $screen-xs-max) { - h4 { - font-size: 14px; - } - p { font-size: 13px; } - .btn, - .btn-group, - .accept-action { - margin-bottom: 4px; - } - .btn-grouped { float: none; margin-right: 0; @@ -367,19 +327,16 @@ } } - &.mr-state-locked .mr-info-list { - margin-top: 10px; - margin-left: 12px; - } + &.mr-widget-empty-state { + line-height: 20px; - &.empty-state { .artwork { margin-bottom: $gl-padding; } .text { span { - font-weight: bold; + font-weight: 600; } p { @@ -389,10 +346,6 @@ } } - .mr-widget-footer { - border-top: 1px solid $gray-darker; - } - .ci-coverage { float: right; } @@ -497,8 +450,6 @@ } .btn-clipboard { - @extend .pull-right; - margin-right: 20px; margin-top: 5px; position: absolute; @@ -506,56 +457,29 @@ } } +.mr-links { + padding-left: $status-icon-size + $status-icon-margin; +} + .mr-info-list { + clear: left; position: relative; - margin: 10px 0 $gl-padding 12px; + padding-top: 4px; p { - margin: 6px 0; + margin: 0; position: relative; - padding-left: 15px; - - &::before { - content: ''; - position: absolute; - border-top: 2px solid $border-color; - height: 1px; - top: 9px; - width: 8px; - left: 0; - } + padding: 4px 0; &:last-child { - margin-bottom: 0; + padding-bottom: 0; } } - - .legend { - height: 100%; - width: 2px; - background: $border-color; - position: absolute; - top: -9px; - } } .mr-info-list.mr-memory-usage { - .legend { - height: 65%; - top: 0; - - @media (max-width: $screen-xs-max) { - height: 20px; - } - } - p { float: left; - padding-left: 21px; - - &::before { - top: 13px; - } } .memory-graph-container { @@ -565,12 +489,13 @@ } .mr-source-target { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; background-color: $gray-light; - border-radius: 3px 3px 0 0; - border-bottom: 1px solid $border-color; - padding: 0 $gl-padding; - margin-bottom: 6px; - line-height: 44px; + border-radius: $border-radius-default $border-radius-default 0 0; + padding: $gl-padding / 2 $gl-padding; .dropdown-toggle .fa { color: $gl-text-color; @@ -679,20 +604,16 @@ } .merged-buttons { - margin-top: 20px; - .btn { float: left; - - &:not(:last-child) { - margin-right: 10px; - } } } .mr-version-controls { + position: relative; background: $gray-light; color: $gl-text-color; + z-index: 199; .mr-version-menus-container { display: -webkit-flex; @@ -801,20 +722,8 @@ } .mr-memory-usage { - p.usage-info-loading, - p.usage-info-unavailable, - p.usage-info-failed { - margin-bottom: 5px; - } - p.usage-info-loading .usage-info-load-spinner { margin-right: 10px; font-size: 16px; } - - @media (max-width: $screen-md-min) { - .mr-info-list.mr-memory-usage .legend { - height: 80%; - } - } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index cdb1e65e4be..c90642178fc 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -104,40 +104,51 @@ } .confidential-issue-warning { - background-color: $gray-normal; - border-radius: 3px; + color: $orange-600; + background-color: $orange-50; + border-radius: $border-radius-default $border-radius-default 0 0; + border: 1px solid $border-gray-normal; padding: 3px 12px; margin: auto; - margin-top: 0; - text-align: center; - font-size: 12px; align-items: center; +} - @media (max-width: $screen-md-max) { - // On smaller devices the warning becomes the fourth item in the list, - // rather than centering, and grows to span the full width of the - // comment area. - order: 4; - margin: 6px auto; - width: 100%; +.confidential-value { + .fa { + background-color: inherit; } +} - .fa { - margin-right: 8px; +.confidential-warning-message { + line-height: 1.5; + padding: 16px; + + .confidential-warning-message-actions { + display: flex; + + button { + flex-grow: 1; + } } } +.not-confidential { + padding: 0; + border-top: none; +} + .right-sidebar-expanded { - .confidential-issue-warning { - // When the sidebar is open the warning becomes the fourth item in the list, - // rather than centering, and grows to span the full width of the - // comment area. - order: 4; - margin: 6px auto; - width: 100%; + .md-area { + border-radius: 0; + border-top: none; } } +.right-sidebar-collapsed { + .confidential-issue-warning { + border-bottom: none; + } +} .discussion-form { padding: $gl-padding-top $gl-padding $gl-padding; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index d3862df20d3..6185342b495 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -220,7 +220,11 @@ position: relative; vertical-align: middle; height: 22px; - margin: 3px 6px 3px 0; + margin: 3px 0; + + + .stage-container { + margin-left: 6px; + } // Hack to show a button tooltip inline button.has-tooltip + .tooltip { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b3a90dff89a..276465488e7 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -7,7 +7,8 @@ } .new_project, -.edit-project { +.edit-project, +.import-project { .sharing-and-permissions { .header { @@ -36,7 +37,6 @@ } select { - background: transparent; transition: background 2s ease-out; &.highlight-changes { @@ -282,6 +282,8 @@ } .project-repo-buttons { + @include new-style-dropdown; + .project-action-button .dropdown-menu { max-height: 250px; overflow-y: auto; @@ -456,6 +458,7 @@ a.deploy-project-label { } } +.project-template, .project-import { .form-group { margin-bottom: 5px; @@ -470,7 +473,44 @@ a.deploy-project-label { .btn { padding: 8px; - margin-left: 10px; + margin-right: 10px; + } + + .blank-option { + min-width: 70px; + } + + .btn-template-icon { + height: 24px; + width: inherit; + display: block; + margin: 0 auto 4px; + font-size: 24px; + + @media (min-width: $screen-xs-max) { + top: 0; + } + } + + @media (max-width: $screen-xs-max) { + .btn-template-icon { + display: inline-block; + height: 14px; + font-size: 14px; + margin: 0; + } + } + + .icon-rails path { + fill: $rails; + } + + .icon-node-express path { + fill: $node; + } + + .icon-java-spring path { + fill: $java; } > div { @@ -480,6 +520,97 @@ a.deploy-project-label { } } +.project-templates-buttons .btn:last-child { + margin-right: 0; +} + +.create-project-options { + display: flex; + + @media (max-width: $screen-xs-max) { + display: block; + } + + .first-column { + @media(min-width: $screen-xs-min) { + max-width: 50%; + padding-right: 30px; + } + + @media(max-width: $screen-xs-max) { + max-width: 100%; + width: 100%; + } + } + + .second-column { + @media(min-width: $screen-xs-min) { + width: 50%; + flex: 1; + padding-left: 30px; + position: relative; + } + + @media(max-width: $screen-xs-max) { + max-width: 100%; + width: 100%; + padding-left: 0; + position: relative; + } + + // Mobile + @media (max-width: $screen-xs-max) { + padding-top: 30px; + } + + &::before { + content: "OR"; + position: absolute; + left: 0; + top: 40%; + z-index: 10; + padding: 8px 0; + text-align: center; + background-color: $white-light; + color: $gl-text-color-tertiary; + transform: translateX(-50%); + font-size: 12px; + font-weight: bold; + line-height: 20px; + + // Mobile + @media (max-width: $screen-xs-max) { + left: 50%; + top: 10px; + transform: translateY(-50%); + padding: 0 8px; + } + } + + &::after { + content: ""; + position: absolute; + background-color: $border-color; + bottom: 0; + left: 0; + right: auto; + height: 100%; + width: 1px; + top: 0; + + // Mobile + @media (max-width: $screen-xs-max) { + top: 10px; + left: 10px; + right: 10px; + height: 1px; + width: auto; + } + } + } +} + + .project-stats { font-size: 0; text-align: center; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss new file mode 100644 index 00000000000..ad17078c98a --- /dev/null +++ b/app/assets/stylesheets/pages/repo.scss @@ -0,0 +1,413 @@ +.fade-enter-active, +.fade-leave-active { + transition: opacity .5s; +} + +.monaco-loader { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: $black-transparent; +} + +.modal.popup-dialog { + display: block; + background-color: $black-transparent; + z-index: 2100; + + @media (min-width: $screen-md-min) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + } +} + +.project-refs-form, +.project-refs-target-form { + display: inline-block; + + &.disabled { + opacity: 0.5; + pointer-events: none; + } +} + +.fade-enter, +.fade-leave-to { + opacity: 0; +} + +.commit-message { + @include str-truncated(250px); +} + +.editable-mode { + display: inline-block; +} + +.blob-viewer[data-type="rich"] { + margin: 20px; +} + +.repository-view.tree-content-holder { + border: 1px solid $border-color; + border-radius: $border-radius-default; + color: $almost-black; + + .panel-right { + display: inline-block; + width: 80%; + + .monaco-editor.vs { + .line-numbers { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + + .cursor { + display: none !important; + } + } + + &.edit-mode { + .blob-viewer-container { + overflow: hidden; + } + + .monaco-editor.vs { + .cursor { + background: $black; + border-color: $black; + display: block !important; + } + } + } + + .blob-viewer-container { + height: calc(100vh - 63px); + overflow: auto; + } + + #tabs { + padding-left: 0; + margin-bottom: 0; + display: flex; + white-space: nowrap; + width: 100%; + overflow-y: hidden; + overflow-x: auto; + + li { + animation: swipeRightAppear ease-in 0.1s; + animation-iteration-count: 1; + transform-origin: 0% 50%; + list-style-type: none; + background: $gray-normal; + display: inline-block; + padding: 10px 18px; + border-right: 1px solid $white-dark; + border-bottom: 1px solid $white-dark; + white-space: nowrap; + + &.remove { + animation: swipeRightDissapear ease-in 0.1s; + animation-iteration-count: 1; + transform-origin: 0% 50%; + + a { + width: 0; + } + } + + &.active { + background: $white-light; + border-bottom: none; + } + + a { + @include str-truncated(100px); + color: $black; + display: inline-block; + width: 100px; + text-align: center; + vertical-align: middle; + + &.close { + width: auto; + font-size: 15px; + opacity: 1; + margin-right: -6px; + } + } + + i.fa.fa-times, + i.fa.fa-circle { + float: right; + margin-top: 3px; + margin-left: 15px; + color: $gray-darkest; + } + + i.fa.fa-circle { + color: $brand-success; + } + + &.tabs-divider { + width: 100%; + background-color: $white-light; + border-right: none; + border-top-right-radius: 2px; + } + } + } + + #repo-file-buttons { + background-color: $white-light; + border-bottom: 1px solid $white-normal; + padding: 5px 10px; + position: relative; + border-top: 1px solid $white-normal; + margin-top: -5px; + } + + #binary-viewer { + height: 80vh; + overflow: auto; + margin: 0; + + .blob-viewer { + padding-top: 20px; + padding-left: 20px; + } + + .binary-unknown { + text-align: center; + padding-top: 100px; + background: $gray-light; + height: 100%; + font-size: 17px; + + span { + display: block; + } + } + } + } + + #commit-area { + background: $gray-light; + padding: 20px; + + span.help-block { + padding-top: 7px; + margin-top: 0; + } + } + + #view-toggler { + height: 41px; + position: relative; + display: block; + border-bottom: 1px solid $white-normal; + background: $white-light; + margin-top: -5px; + } + + #binary-viewer { + img { + max-width: 100%; + } + } + + #sidebar { + + &.sidebar-mini { + display: inline-block; + vertical-align: top; + width: 20%; + border-right: 1px solid $white-normal; + height: calc(100vh + 20px); + overflow: auto; + } + + table { + margin-bottom: 0; + } + + tr { + animation: fadein 0.5s; + cursor: pointer; + + &.repo-file-options td { + padding: 0; + border-top: none; + background: $gray-light; + width: 100%; + display: inline-block; + + &:first-child { + border-top-left-radius: 2px; + } + + .title { + display: inline-block; + font-size: 10px; + text-transform: uppercase; + font-weight: bold; + color: $gray-darkest; + width: 185px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + padding: 2px 16px; + } + } + + .fa { + margin-right: 5px; + } + + td { + white-space: nowrap; + } + } + + a { + color: $almost-black; + display: inline-block; + vertical-align: middle; + } + + ul { + list-style-type: none; + padding: 0; + + li { + border-bottom: 1px solid $border-gray-normal; + padding: 10px 20px; + + a { + color: $almost-black; + } + + .fa { + font-size: $code_font_size; + margin-right: 5px; + } + } + } + } + +} + +.animation-container { + background: $repo-editor-grey; + height: 40px; + overflow: hidden; + position: relative; + + &.animation-container-small { + height: 12px; + } + + &::before { + animation-duration: 1s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: blockTextShine; + animation-timing-function: linear; + background-image: $repo-editor-linear-gradient; + background-repeat: no-repeat; + background-size: 800px 45px; + content: ' '; + display: block; + height: 100%; + position: relative; + } + + div { + background: $white-light; + height: 6px; + left: 0; + position: absolute; + right: 0; + } + + .line-of-code-1 { + left: 0; + top: 8px; + } + + .line-of-code-2 { + left: 150px; + top: 0; + height: 10px; + } + + .line-of-code-3 { + left: 0; + top: 23px; + } + + .line-of-code-4 { + left: 0; + top: 38px; + } + + .line-of-code-5 { + left: 200px; + top: 28px; + height: 10px; + } + + .line-of-code-6 { + top: 14px; + left: 230px; + height: 10px; + } +} + +.render-error { + min-height: calc(100vh - 63px); + + p { + width: 100%; + } +} + +@keyframes blockTextShine { + 0% { + transform: translateX(-468px); + } + + 100% { + transform: translateX(468px); + } +} + +@keyframes swipeRightAppear { + 0% { + transform: scaleX(0.00); + } + + 100% { + transform: scaleX(1.00); + } +} + +@keyframes swipeRightDissapear { + 0% { + transform: scaleX(1.00); + } + + 100% { + transform: scaleX(0.00); + } +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index d69a8e0995c..15df51e9c69 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -54,8 +54,7 @@ .settings-content { max-height: 1px; overflow-y: scroll; - margin-right: -20px; - padding-right: 130px; + padding-right: 110px; animation: collapseMaxHeight 300ms ease-out; &.expanded { @@ -87,6 +86,23 @@ overflow: hidden; margin-top: 20px; } + + .sub-section { + margin-bottom: 32px; + padding: 16px; + border: 1px solid $border-color; + background-color: $gray-light; + } + + .bs-callout, + .checkbox:first-child, + .help-block { + margin-top: 0; + } + + .label-light { + margin-bottom: 0; + } } .settings-list-icon { diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index e0f46172769..11236cbf2e7 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -1,4 +1,5 @@ .tree-holder { + @include new-style-dropdown; .nav-block { margin: 10px 0; @@ -86,7 +87,7 @@ } .add-to-tree { - vertical-align: top; + vertical-align: middle; padding: 6px 10px; } @@ -202,28 +203,6 @@ } } } - - // TODO: fallback to global style - .dropdown-menu:not(.dropdown-menu-selectable) { - li { - padding: 0 1px; - - &.dropdown-header { - padding: 8px 16px; - } - - a { - border-radius: 0; - padding: 8px 16px; - - &:hover, - &:active, - &:focus { - background-color: $gray-darker; - } - } - } - } } .blob-commit-info { @@ -237,6 +216,9 @@ } .blob-upload-dropzone-previews { + display: flex; + justify-content: center; + align-items: center; text-align: center; border: 2px; border-style: dashed; diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 45c21c5d274..fa6bdd297eb 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -95,12 +95,22 @@ } .right-sidebar.wiki-sidebar { - padding: $gl-padding 0; + padding: 0; &.right-sidebar-collapsed { display: none; } + .sidebar-container { + padding: $gl-padding 0; + width: calc(100% + 100px); + padding-right: 100px; + height: 100%; + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + .blocks-container { padding: 0 $gl-padding; } diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb index caf4c138da8..65a17828feb 100644 --- a/app/controllers/admin/health_check_controller.rb +++ b/app/controllers/admin/health_check_controller.rb @@ -1,5 +1,12 @@ class Admin::HealthCheckController < Admin::ApplicationController def show @errors = HealthCheck::Utils.process_checks(['standard']) + @failing_storage_statuses = Gitlab::Git::Storage::Health.for_failing_storages + end + + def reset_storage_health + Gitlab::Git::Storage::CircuitBreaker.reset_all! + redirect_to admin_health_check_path, + notice: _('Git storage health information has been reset') end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d14b1dbecf6..1d92ea11bda 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -52,6 +52,15 @@ class ApplicationController < ActionController::Base head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window end + rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception| + Raven.capture_exception(exception) if sentry_enabled? + log_exception(exception) + + headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after) + + render_503 + end + def redirect_back_or_default(default: root_path, options: {}) redirect_to request.referer.present? ? :back : default, options end @@ -108,7 +117,7 @@ class ApplicationController < ActionController::Base Raven.capture_exception(exception) if sentry_enabled? application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace - application_trace.map!{ |t| " #{t}\n" } + application_trace.map! { |t| " #{t}\n" } logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" end @@ -152,6 +161,19 @@ class ApplicationController < ActionController::Base head :unprocessable_entity end + def render_503 + respond_to do |format| + format.html do + render( + file: Rails.root.join("public", "503"), + layout: false, + status: :service_unavailable + ) + end + format.any { head :service_unavailable } + end + end + def no_cache_headers response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb index 54dcd7c61ce..ba7adcfea86 100644 --- a/app/controllers/concerns/renders_blob.rb +++ b/app/controllers/concerns/renders_blob.rb @@ -1,7 +1,7 @@ module RendersBlob extend ActiveSupport::Concern - def render_blob_json(blob) + def blob_json(blob) viewer = case params[:viewer] when 'rich' @@ -11,13 +11,21 @@ module RendersBlob else blob.simple_viewer end - return render_404 unless viewer - render json: { + return unless viewer + + { html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_async: false) } end + def render_blob_json(blob) + json = blob_json(blob) + return render_404 unless json + + render json: json + end + def conditionally_expand_blob(blob) blob.expand! if params[:expanded] == 'true' end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 91c1e4dff79..74fe45e1ff6 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -45,8 +45,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def load_projects(finder_params) - ProjectsFinder.new(params: finder_params, current_user: current_user) - .execute.includes(:route, namespace: :route) + ProjectsFinder + .new(params: finder_params, current_user: current_user) + .execute + .includes(:route, :creator, namespace: :route) end def load_events diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 59e5b5e4775..a8b2b93b458 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -13,7 +13,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy - TodoService.new.mark_todos_as_done_by_ids([params[:id]], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) respond_to do |format| format.html do @@ -37,7 +37,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def restore - TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user) + TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user) render json: todos_counts end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 53a5981e564..baa6645e5ce 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -68,15 +68,15 @@ class Import::GithubController < Import::BaseController end def new_import_url - public_send("new_import_#{provider}_url") + public_send("new_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def status_import_url - public_send("status_import_#{provider}_url") + public_send("status_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def callback_import_url - public_send("callback_import_#{provider}_url") + public_send("callback_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def provider_unauthorized diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 73837ffbe67..407154e59a0 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -15,7 +15,7 @@ class Import::GitlabController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitlab") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + @repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] } end def jobs diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 36d246d185b..510813846a4 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end - import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename) - - FileUtils.mkdir_p(File.dirname(import_upload_path)) - FileUtils.copy_entry(project_params[:file].path, import_upload_path) - - @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], - current_user, - import_upload_path, - project_params[:path]).execute + @project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute if @project.saved? redirect_to( diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 323d5d26eb6..b4213574561 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -34,12 +34,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.two_factor_enabled? prompt_for_two_factor(@user) else - log_audit_event(@user, with: :ldap) + log_audit_event(@user, with: oauth['provider']) sign_in_and_redirect(@user) end else - flash[:alert] = "Access denied for your LDAP account." - redirect_to new_user_session_path + fail_ldap_login end end @@ -123,9 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController sign_in_and_redirect(@user) end else - error_message = @user.errors.full_messages.to_sentence - - return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + fail_login end end @@ -145,6 +142,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def oauth @oauth ||= request.env['omniauth.auth'] end + + def fail_login + error_message = @user.errors.full_messages.to_sentence + + return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + end + + def fail_ldap_login + flash[:alert] = 'Access denied for your LDAP account.' + + redirect_to new_user_session_path + end def log_audit_event(user, options = {}) AuditEventService.new(user, user, options) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 49ea2945675..a2e8c10857d 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -37,16 +37,11 @@ class Projects::BlobController < Projects::ApplicationController respond_to do |format| format.html do - environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } - @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last - - @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) - - render 'show' + show_html end format.json do - render_blob_json(@blob) + show_json end end end @@ -190,4 +185,34 @@ class Projects::BlobController < Projects::ApplicationController @last_commit_sha = Gitlab::Git::Commit .last_for_path(@repository, @ref, @path).sha end + + def show_html + environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } + @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last + @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) + + render 'show' + end + + def show_json + json = blob_json(@blob) + return render_404 unless json + + render json: json.merge( + path: blob.path, + name: blob.name, + extension: blob.extension, + size: blob.raw_size, + mime_type: blob.mime_type, + binary: blob.raw_binary?, + simple_viewer: blob.simple_viewer&.class&.partial_name, + rich_viewer: blob.rich_viewer&.class&.partial_name, + show_viewer_switcher: !!blob.show_viewer_switcher?, + render_error: blob.simple_viewer&.render_error || blob.rich_viewer&.render_error, + raw_path: project_raw_path(project, @id), + blame_path: project_blame_path(project, @id), + commits_path: project_commits_path(project, @id), + permalink: project_blob_path(project, File.join(@commit.id, @path)) + ) + end end diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index da9b789d617..653e7bc7e40 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -66,7 +66,8 @@ module Projects end def filter_params - params.merge(board_id: params[:board_id], id: params[:list_id]).compact + params.merge(board_id: params[:board_id], id: params[:list_id]) + .reject { |_, value| value.nil? } end def move_params diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 57372f9e79d..475d4c86294 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -43,23 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController end def get_languages - @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages - total = @languages.map(&:last).sum - - @languages = @languages.map do |language| - name, share = language - color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" - { - value: (share.to_f * 100 / total).round(2), - label: name, - color: color, - highlight: color - } - end - - @languages.sort! do |x, y| - y[:value] <=> x[:value] - end + @languages = @project.repository.languages end def fetch_graph diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d361e661d0e..4de814d0ca8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -67,11 +67,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @noteable = @merge_request @commits_count = @merge_request.commits_count - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end - labels set_pipeline_variables diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 30181ac3bdf..1fc276b8c03 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -24,12 +24,19 @@ class Projects::TreeController < Projects::ApplicationController end end - @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit - respond_to do |format| - format.html - # Disable cache so browser history works - format.js { no_cache_headers } + format.html do + @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit + end + + format.js do + # Disable cache so browser history works + no_cache_headers + end + + format.json do + render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree) + end end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2d7cbd4614e..8dfe0f51709 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -220,21 +220,34 @@ class ProjectsController < Projects::ApplicationController end def refs - branches = BranchesFinder.new(@repository, params).execute.map(&:name) + find_refs = params['find'] - options = { - s_('RefSwitcher|Branches') => branches.take(100) - } + find_branches = true + find_tags = true + find_commits = true + + unless find_refs.nil? + find_branches = find_refs.include?('branches') + find_tags = find_refs.include?('tags') + find_commits = find_refs.include?('commits') + end + + options = {} + + if find_branches + branches = BranchesFinder.new(@repository, params).execute.take(100).map(&:name) + options[s_('RefSwitcher|Branches')] = branches + end - unless @repository.tag_count.zero? - tags = TagsFinder.new(@repository, params).execute.map(&:name) + if find_tags && @repository.tag_count.nonzero? + tags = TagsFinder.new(@repository, params).execute.take(100).map(&:name) - options[s_('RefSwitcher|Tags')] = tags.take(100) + options[s_('RefSwitcher|Tags')] = tags end # If reference is commit id - we should add it to branch/tag selectbox ref = Addressable::URI.unescape(params[:ref]) - if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ + if find_commits && ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ options['Commits'] = [ref] end @@ -324,6 +337,7 @@ class ProjectsController < Projects::ApplicationController :runners_token, :tag_list, :visibility_level, + :template_name, project_feature_attributes: %i[ builds_access_level diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 3fe37c75381..b276116f0c6 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -95,9 +95,18 @@ class TodosFinder @project end + def project_ids(items) + ids = items.except(:order).select(:project_id) + if Gitlab::Database.mysql? + # To make UPDATE work on MySQL, wrap it in a SELECT with an alias + ids = Todo.except(:order).select('*').from("(#{ids.to_sql}) AS t") + end + + ids + end + def projects(items) - item_project_ids = items.reorder(nil).select(:project_id) - ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute + ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute end def type? diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 14dc9bd9d62..bcee81bdc15 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -305,4 +305,12 @@ module ApplicationHelper def show_new_nav? cookies["new_nav"] == "true" end + + def collapsed_sidebar? + cookies["sidebar_collapsed"] == "true" + end + + def show_new_repo? + cookies["new_repo"] == "true" && body_data_page != 'projects:show' + end end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index 0e068d4b51c..4b51269533c 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -19,7 +19,8 @@ module AvatarsHelper class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]), alt: "#{user_name}'s avatar", title: user_name, - data: data_attributes + data: data_attributes, + lazy: true ) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index e964d7a5e16..18075ee8be7 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -118,7 +118,7 @@ module BlobHelper icon("#{file_type_icon_class('file', mode, name)} fw") end - def blob_raw_url + def blob_raw_path if @build && @entry raw_project_job_artifacts_path(@project, @build, path: @entry.path) elsif @snippet @@ -235,7 +235,7 @@ module BlobHelper title = 'Open raw' end - link_to icon, blob_raw_url, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' } + link_to icon, blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' } end def blob_render_error_reason(viewer) @@ -270,7 +270,7 @@ module BlobHelper options << link_to('view the source', '#', class: 'js-blob-viewer-switch-btn', data: { viewer: 'simple' }) end - options << link_to('download it', blob_raw_url, target: '_blank', rel: 'noopener noreferrer') + options << link_to('download it', blob_raw_path, target: '_blank', rel: 'noopener noreferrer') options end diff --git a/app/helpers/defer_script_tag_helper.rb b/app/helpers/defer_script_tag_helper.rb new file mode 100644 index 00000000000..e1567556e5e --- /dev/null +++ b/app/helpers/defer_script_tag_helper.rb @@ -0,0 +1,6 @@ +module DeferScriptTagHelper + # Override the default ActionView `javascript_include_tag` helper to support page specific deferred loading + def javascript_include_tag(*sources) + super(*sources, defer: true) + end +end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 91ddd73fac1..28f591a4e22 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -88,15 +88,15 @@ module DiffHelper end def submodule_link(blob, ref, repository = @repository) - tree, commit = submodule_links(blob, ref, repository) - commit_id = if commit.nil? + project_url, tree_url = submodule_links(blob, ref, repository) + commit_id = if tree_url.nil? Commit.truncate_sha(blob.id) else - link_to Commit.truncate_sha(blob.id), commit + link_to Commit.truncate_sha(blob.id), tree_url end [ - content_tag(:span, link_to(truncate(blob.name, length: 40), tree)), + content_tag(:span, link_to(truncate(blob.name, length: 40), project_url)), '@', content_tag(:span, commit_id, class: 'commit-sha') ].join(' ').html_safe @@ -148,6 +148,24 @@ module DiffHelper options end + def diff_file_changed_icon(diff_file) + if diff_file.deleted_file? || diff_file.renamed_file? + "minus" + elsif diff_file.new_file? + "plus" + else + "adjust" + end + end + + def diff_file_changed_icon_color(diff_file) + if diff_file.deleted_file? + "cred" + elsif diff_file.new_file? + "cgreen" + end + end + private def diff_btn(title, name, selected) diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index ac8c518ac84..ff305fa39b4 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -48,11 +48,11 @@ module DropdownsHelper end end - def dropdown_title(title, back: false) + def dropdown_title(title, options: {}) content_tag :div, class: "dropdown-title" do title_output = "" - if back + if options.fetch(:back, false) title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do icon('arrow-left') end @@ -60,14 +60,25 @@ module DropdownsHelper title_output << content_tag(:span, title) - title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do - icon('times', class: 'dropdown-menu-close-icon') + if options.fetch(:close, true) + title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do + icon('times', class: 'dropdown-menu-close-icon') + end end title_output.html_safe end end + def dropdown_input(placeholder, input_id: nil) + content_tag :div, class: "dropdown-input" do + filter_output = text_field_tag input_id, nil, class: "dropdown-input-field dropdown-no-filter", placeholder: placeholder, autocomplete: 'off' + filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button") + + filter_output.html_safe + end + end + def dropdown_filter(placeholder, search_id: nil) content_tag :div, class: "dropdown-input" do filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 1f7db9b2eb8..d4a91e533c1 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -47,14 +47,6 @@ module GitlabRoutingHelper project_pipeline_path(pipeline.project, pipeline.id, *args) end - def milestone_path(entity, *args) - if entity.is_group_milestone? - group_milestone_path(entity.group, entity, *args) - elsif entity.is_project_milestone? - project_milestone_path(entity.project, entity, *args) - end - end - def issue_url(entity, *args) project_issue_url(entity.project, entity, *args) end @@ -67,14 +59,6 @@ module GitlabRoutingHelper project_pipeline_url(pipeline.project, pipeline.id, *args) end - def milestone_url(entity, *args) - if entity.is_group_milestone? - group_milestone_url(entity.group, entity, *args) - elsif entity.is_project_milestone? - project_milestone_url(entity.project, entity, *args) - end - end - def pipeline_job_url(pipeline, build, *args) project_job_url(pipeline.project, build.id, *args) end diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 2e9b72e9613..c53ea4519da 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -3,7 +3,7 @@ module GraphHelper refs = "" # Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX) # so anything leftover is internally used by GitLab - commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') } + commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') } refs << commit_refs.join(' ') # append note count diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index f29faeca22d..9a404832423 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -1,4 +1,5 @@ module IconsHelper + extend self include FontAwesome::Rails::IconHelper # Creates an icon tag given icon name(s) and possible icon modifiers. diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index f4fad7150e8..70ea35fab1e 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -151,7 +151,7 @@ module IssuablesHelper end def issuable_labels_tooltip(labels, limit: 5) - first, last = labels.partition.with_index{ |_, i| i < limit } + first, last = labels.partition.with_index { |_, i| i < limit } label_names = first.collect(&:name) label_names << "and #{last.size} more" unless last.empty? @@ -234,7 +234,7 @@ module IssuablesHelper end def issuables_count_for_state(issuable_type, state, finder: nil) - finder ||= public_send("#{issuable_type}_finder") + finder ||= public_send("#{issuable_type}_finder") # rubocop:disable GitlabSecurity/PublicSend cache_key = finder.state_counter_cache_key @counts ||= {} @@ -329,7 +329,7 @@ module IssuablesHelper end def selected_template(issuable) - params[:issuable_template] if issuable_templates(issuable).any?{ |template| template[:name] == params[:issuable_template] } + params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } end def issuable_todo_button_data(issuable, todo, is_collapsed) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4b99de1b6a5..e60513b35c7 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -43,11 +43,11 @@ module LabelsHelper def label_filter_path(subject, label, type: :issue) case subject when Group - send("#{type.to_s.pluralize}_group_path", + send("#{type.to_s.pluralize}_group_path", # rubocop:disable GitlabSecurity/PublicSend subject, label_name: [label.name]) when Project - send("namespace_project_#{type.to_s.pluralize}_path", + send("namespace_project_#{type.to_s.pluralize}_path", # rubocop:disable GitlabSecurity/PublicSend subject.namespace, subject, label_name: [label.name]) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 78cf7b26a31..c31023f2d9a 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -40,7 +40,7 @@ module MergeRequestsHelper def merge_path_description(merge_request, separator) if merge_request.for_fork? - "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" + "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.full_path}:#{@merge_request.target_branch}" else "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" end diff --git a/app/helpers/milestones_routing_helper.rb b/app/helpers/milestones_routing_helper.rb new file mode 100644 index 00000000000..766d5262018 --- /dev/null +++ b/app/helpers/milestones_routing_helper.rb @@ -0,0 +1,17 @@ +module MilestonesRoutingHelper + def milestone_path(milestone, *args) + if milestone.is_group_milestone? + group_milestone_path(milestone.group, milestone, *args) + elsif milestone.is_project_milestone? + project_milestone_path(milestone.project, milestone, *args) + end + end + + def milestone_url(milestone, *args) + if milestone.is_group_milestone? + group_milestone_url(milestone.group, milestone, *args) + elsif milestone.is_project_milestone? + project_milestone_url(milestone.project, milestone, *args) + end + end +end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index b1205b8529b..b63b3b70903 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -2,6 +2,7 @@ module NavHelper def page_with_sidebar_class class_name = page_gutter_class class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar + class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar class_name end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 9a8d296d514..a268413e84f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -225,6 +225,26 @@ module ProjectsHelper end end + # Returns true if any projects are present. + # + # If the relation has a LIMIT applied we'll cast the relation to an Array + # since repeated any? checks would otherwise result in multiple COUNT queries + # being executed. + # + # If no limit is applied we'll just issue a COUNT since the result set could + # be too large to load into memory. + def any_projects?(projects) + if projects.limit_value + projects.to_a.any? + else + projects.except(:offset).any? + end + end + + def has_projects_or_name?(projects, params) + !!(params[:name] || any_projects?(projects)) + end + private def repo_children_classes(field) @@ -398,7 +418,7 @@ module ProjectsHelper if project import_path = "/Home/Stacks/import" - repo = project.path_with_namespace + repo = project.full_path branch ||= project.default_branch sha ||= project.commit.short_id @@ -458,7 +478,7 @@ module ProjectsHelper def readme_cache_key sha = @project.commit.try(:sha) || 'nil' - [@project.path_with_namespace, sha, "readme"].join('-') + [@project.full_path, sha, "readme"].join('-') end def current_ref diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index fd7ab59ce64..ae0e0aa3cf9 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -127,15 +127,23 @@ module SearchHelper end def search_filter_input_options(type) - { + opts = { id: "filtered-search-#{type}", placeholder: 'Search or filter results...', data: { - 'project-id' => @project.id, - 'username-params' => @users.to_json(only: [:id, :username]), - 'base-endpoint' => project_path(@project) + 'username-params' => @users.to_json(only: [:id, :username]) } } + + if @project.present? + opts[:data]['project-id'] = @project.id + opts[:data]['base-endpoint'] = project_path(@project) + else + # Group context + opts[:data]['base-endpoint'] = group_canonical_path(@group) + end + + opts end # Sanitize a HTML field for search display. Most tags are stripped out and the diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb new file mode 100644 index 00000000000..544c9efb845 --- /dev/null +++ b/app/helpers/storage_health_helper.rb @@ -0,0 +1,37 @@ +module StorageHealthHelper + def failing_storage_health_message(storage_health) + storage_name = content_tag(:strong, h(storage_health.storage_name)) + host_names = h(storage_health.failing_on_hosts.to_sentence) + translation_params = { storage_name: storage_name, + host_names: host_names, + failed_attempts: storage_health.total_failures } + + translation = n_('%{storage_name}: failed storage access attempt on host:', + '%{storage_name}: %{failed_attempts} failed storage access attempts:', + storage_health.total_failures) % translation_params + + translation.html_safe + end + + def message_for_circuit_breaker(circuit_breaker) + maximum_failures = circuit_breaker.failure_count_threshold + current_failures = circuit_breaker.failure_count + permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures + + translation_params = { number_of_failures: current_failures, + maximum_failures: maximum_failures, + number_of_seconds: circuit_breaker.failure_wait_time } + + if permanently_broken + s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\ + "retry automatically. Reset storage information when the problem is "\ + "resolved.") % translation_params + elsif circuit_breaker.circuit_broken? + _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\ + "block access for %{number_of_seconds} seconds.") % translation_params + else + _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\ + "allow access on the next attempt.") % translation_params + end + end +end diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index b24039fb349..88f7702db1e 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -1,5 +1,5 @@ module SubmoduleHelper - include Gitlab::ShellAdapter + extend self VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze @@ -59,7 +59,7 @@ module SubmoduleHelper return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/', project].join('') url_with_dotgit = url_no_dotgit + '.git' - url_with_dotgit == gitlab_shell.url_to_repo([namespace, '/', project].join('')) + url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join('')) end def relative_self_url?(url) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index eaac6fcb548..9efabe3f44e 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -165,7 +165,7 @@ class Notify < BaseMailer headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id - headers['X-GitLab-Project-Path'] = @project.path_with_namespace + headers['X-GitLab-Project-Path'] = @project.full_path end def add_unsubscription_headers_and_links diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index 35965d01692..bf3453b3063 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -82,7 +82,7 @@ module BlobViewer # format of the blob. # # Prefer to implement a client-side viewer, where the JS component loads the - # binary from `blob_raw_url` and does its own format validation and error + # binary from `blob_raw_path` and does its own format validation and error # rendering, especially for potentially large binary formats. def render_error if too_large? diff --git a/app/models/blob_viewer/server_side.rb b/app/models/blob_viewer/server_side.rb index fbc1b520c01..86afcc86aa0 100644 --- a/app/models/blob_viewer/server_side.rb +++ b/app/models/blob_viewer/server_side.rb @@ -17,7 +17,7 @@ module BlobViewer # build artifacts, can only be rendered using a client-side viewer, # since we do not want to read large amounts of data into memory on the # server side. Client-side viewers use JS and can fetch the file from - # `blob_raw_url` using AJAX. + # `blob_raw_path` using AJAX. return :server_side_but_stored_externally if blob.stored_externally? super diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d2abcf30034..ea7331cb27f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -317,7 +317,7 @@ module Ci return @config_processor if defined?(@config_processor) @config_processor ||= begin - Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) + Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.full_path) rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e self.yaml_errors = e.message nil diff --git a/app/models/commit.rb b/app/models/commit.rb index 7940733f557..638fddc5d3d 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -55,7 +55,8 @@ class Commit end def from_hash(hash, project) - new(Gitlab::Git::Commit.new(hash), project) + raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash) + new(raw_commit, project) end def valid_hash?(key) @@ -320,21 +321,11 @@ class Commit end def raw_diffs(*args) - if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs) - Gitlab::GitalyClient::CommitService.new(project.repository).diff_from_parent(self, *args) - else - raw.diffs(*args) - end + raw.diffs(*args) end def raw_deltas - @deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled| - if is_enabled - Gitlab::GitalyClient::CommitService.new(project.repository).commit_deltas(self) - else - raw.deltas - end - end + @deltas ||= raw.deltas end def diffs(diff_options = nil) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 935ffe343ff..3731b7c8577 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -16,6 +16,7 @@ module Issuable include TimeTrackable include Importable include Editable + include AfterCommitQueue # This object is used to gather issuable meta data for displaying # upvotes, downvotes, notes and closing merge requests count for issues and merge requests diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index a40148a4394..fde1cc44afa 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -1,6 +1,12 @@ module ProtectedBranchAccess extend ActiveSupport::Concern + ALLOWED_ACCESS_LEVELS ||= [ + Gitlab::Access::MASTER, + Gitlab::Access::DEVELOPER, + Gitlab::Access::NO_ACCESS + ].freeze + included do include ProtectedRefAccess @@ -9,11 +15,7 @@ module ProtectedBranchAccess delegate :project, to: :protected_branch validates :access_level, presence: true, inclusion: { - in: [ - Gitlab::Access::MASTER, - Gitlab::Access::DEVELOPER, - Gitlab::Access::NO_ACCESS - ] + in: ALLOWED_ACCESS_LEVELS } def self.human_access_levels diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index da803c7f481..10f4be72016 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -25,6 +25,18 @@ module Referable to_reference(from_project) end + def referable_inspect + if respond_to?(:id) + "#<#{self.class.name} id:#{id} #{to_reference(full: true)}>" + else + "#<#{self.class.name} #{to_reference(full: true)}>" + end + end + + def inspect + referable_inspect + end + module ClassMethods # The character that prefixes the actual reference identifier # diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index bd75f25a210..f2707022a4b 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -58,7 +58,7 @@ module Spammable options.fetch(:spam_title, false) end - public_send(attr.first) if attr && respond_to?(attr.first.to_sym) + public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend end def spam_description @@ -66,12 +66,12 @@ module Spammable options.fetch(:spam_description, false) end - public_send(attr.first) if attr && respond_to?(attr.first.to_sym) + public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend end def spammable_text result = self.class.spammable_attrs.map do |attr| - public_send(attr.first) + public_send(attr.first) # rubocop:disable GitlabSecurity/PublicSend end result.reject(&:blank?).join("\n") diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb new file mode 100644 index 00000000000..5ab5c80a2f5 --- /dev/null +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -0,0 +1,102 @@ +module Storage + module LegacyNamespace + extend ActiveSupport::Concern + + def move_dir + if any_project_has_container_registry_tags? + raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') + end + + # Move the namespace directory in all storage paths used by member projects + repository_storage_paths.each do |repository_storage_path| + # Ensure old directory exists before moving it + gitlab_shell.add_namespace(repository_storage_path, full_path_was) + + unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) + Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" + + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Gitlab::UpdatePathError.new('namespace directory cannot be moved') + end + end + + Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path) + Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path) + + remove_exports! + + # If repositories moved successfully we need to + # send update instructions to users. + # However we cannot allow rollback since we moved namespace dir + # So we basically we mute exceptions in next actions + begin + send_update_instructions + true + rescue + # Returning false does not rollback after_* transaction but gives + # us information about failing some of tasks + false + end + end + + # Hooks + + # Save the storage paths before the projects are destroyed to use them on after destroy + def prepare_for_destroy + old_repository_storage_paths + end + + private + + def old_repository_storage_paths + @old_repository_storage_paths ||= repository_storage_paths + end + + def repository_storage_paths + # We need to get the storage paths for all the projects, even the ones that are + # pending delete. Unscoping also get rids of the default order, which causes + # problems with SELECT DISTINCT. + Project.unscoped do + all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) + end + end + + def rm_dir + # Remove the namespace directory in all storages paths used by member projects + old_repository_storage_paths.each do |repository_storage_path| + # Move namespace directory into trash. + # We will remove it later async + new_path = "#{full_path}+#{id}+deleted" + + if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path) + Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}") + + # Remove namespace directroy async with delay so + # GitLab has time to remove all projects first + run_after_commit do + GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) + end + end + end + + remove_exports! + end + + def remove_exports! + Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) + end + + def export_path + File.join(Gitlab::ImportExport.storage_path, full_path_was) + end + + def full_path_was + if parent + parent.full_path + '/' + path_was + else + path_was + end + end + end +end diff --git a/app/models/concerns/storage/legacy_project.rb b/app/models/concerns/storage/legacy_project.rb new file mode 100644 index 00000000000..815db712285 --- /dev/null +++ b/app/models/concerns/storage/legacy_project.rb @@ -0,0 +1,76 @@ +module Storage + module LegacyProject + extend ActiveSupport::Concern + + def disk_path + full_path + end + + def ensure_storage_path_exist + gitlab_shell.add_namespace(repository_storage_path, namespace.full_path) + end + + def rename_repo + path_was = previous_changes['path'].first + old_path_with_namespace = File.join(namespace.full_path, path_was) + new_path_with_namespace = File.join(namespace.full_path, path) + + Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}" + + if has_container_registry_tags? + Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!" + + # we currently doesn't support renaming repository if it contains images in container registry + raise StandardError.new('Project cannot be renamed, because images are present in its container registry') + end + + expire_caches_before_rename(old_path_with_namespace) + + if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) + # If repository moved successfully we need to send update instructions to users. + # However we cannot allow rollback since we moved repository + # So we basically we mute exceptions in next actions + begin + gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") + send_move_instructions(old_path_with_namespace) + expires_full_path_cache + + @old_path_with_namespace = old_path_with_namespace + + SystemHooksService.new.execute_hooks_for(self, :rename) + + @repository = nil + rescue => e + Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}" + # Returning false does not rollback after_* transaction but gives + # us information about failing some of tasks + false + end + else + Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" + + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise StandardError.new('repository cannot be renamed') + end + + Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" + + Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path) + Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path) + end + + def create_repository(force: false) + # Forked import is handled asynchronously + return if forked? && !force + + if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) + repository.after_create + true + else + errors.add(:base, 'Failed to create repository via gitlab-shell') + false + end + end + end +end diff --git a/app/models/concerns/storage/legacy_project_wiki.rb b/app/models/concerns/storage/legacy_project_wiki.rb new file mode 100644 index 00000000000..ff82cb0ffa9 --- /dev/null +++ b/app/models/concerns/storage/legacy_project_wiki.rb @@ -0,0 +1,9 @@ +module Storage + module LegacyProjectWiki + extend ActiveSupport::Concern + + def disk_path + project.disk_path + '.wiki' + end + end +end diff --git a/app/models/concerns/storage/legacy_repository.rb b/app/models/concerns/storage/legacy_repository.rb new file mode 100644 index 00000000000..593749bf019 --- /dev/null +++ b/app/models/concerns/storage/legacy_repository.rb @@ -0,0 +1,7 @@ +module Storage + module LegacyRepository + extend ActiveSupport::Concern + + delegate :disk_path, to: :project + end +end diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 1ca7f91dc03..a7d5de48c66 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -44,7 +44,8 @@ module TokenAuthenticatable end define_method("ensure_#{token_field}!") do - send("reset_#{token_field}!") if read_attribute(token_field).blank? + send("reset_#{token_field}!") if read_attribute(token_field).blank? # rubocop:disable GitlabSecurity/PublicSend + read_attribute(token_field) end diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb index f42f516f99a..0bee62f954f 100644 --- a/app/models/conversational_development_index/metric.rb +++ b/app/models/conversational_development_index/metric.rb @@ -13,9 +13,7 @@ module ConversationalDevelopmentIndex end def percentage_score(feature) - return 100 if leader_score(feature).zero? - - 100 * instance_score(feature) / leader_score(feature) + self["percentage_#{feature}"] end end end diff --git a/app/models/key.rb b/app/models/key.rb index cb8f10f6d55..49bc26122fa 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -16,8 +16,6 @@ class Key < ActiveRecord::Base presence: true, length: { maximum: 5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ } - validates :key, - format: { without: /\n|\r/, message: 'should be a single line' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } @@ -31,6 +29,7 @@ class Key < ActiveRecord::Base after_destroy :post_destroy_hook def key=(value) + value&.delete!("\n\r") value.strip! unless value.blank? write_attribute(:key, value) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 81e0776e79c..f90194041b1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base include CreatedAtFilterable ignore_column :position + ignore_column :locked_at belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" @@ -61,16 +62,6 @@ class MergeRequest < ActiveRecord::Base transition locked: :opened end - after_transition any => :locked do |merge_request, transition| - merge_request.locked_at = Time.now - merge_request.save - end - - after_transition locked: (any - :locked) do |merge_request, transition| - merge_request.locked_at = nil - merge_request.save - end - state :opened state :closed state :merged @@ -171,7 +162,7 @@ class MergeRequest < ActiveRecord::Base target = unscoped.where(target_project_id: relation).select(:id) union = Gitlab::SQL::Union.new([source, target]) - where("merge_requests.id IN (#{union.to_sql})") + where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection end WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze @@ -392,6 +383,12 @@ class MergeRequest < ActiveRecord::Base 'Source project is not a fork of the target project' end + def merge_ongoing? + return false unless merge_jid + + Gitlab::SidekiqStatus.num_running([merge_jid]) > 0 + end + def closed_without_fork? closed? && source_project_missing? end @@ -630,7 +627,7 @@ class MergeRequest < ActiveRecord::Base def target_project_path if target_project - target_project.path_with_namespace + target_project.full_path else "(removed)" end @@ -638,7 +635,7 @@ class MergeRequest < ActiveRecord::Base def source_project_path if source_project - source_project.path_with_namespace + source_project.full_path else "(removed)" end @@ -725,12 +722,6 @@ class MergeRequest < ActiveRecord::Base end end - def locked_long_ago? - return false unless locked? - - locked_at.nil? || locked_at < (Time.now - 1.day) - end - def has_ci? has_ci_integration = source_project.try(:ci_service) uses_gitlab_ci = all_pipelines.any? diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index ec87aee9310..58050e1f438 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -85,11 +85,7 @@ class MergeRequestDiff < ActiveRecord::Base def raw_diffs(options = {}) if options[:ignore_whitespace_change] - @diffs_no_whitespace ||= - Gitlab::Git::Compare.new( - repository.raw_repository, - safe_start_commit_sha, - head_commit_sha).diffs(options) + @diffs_no_whitespace ||= compare.diffs(options) else @raw_diffs ||= {} @raw_diffs[options] ||= load_diffs(options) @@ -286,9 +282,7 @@ class MergeRequestDiff < ActiveRecord::Base def load_commits commits = st_commits.presence || merge_request_diff_commits - commits.map do |commit| - Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project) - end + commits.map { |commit| Commit.from_hash(commit.to_hash, project) } end def save_diffs diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb index cafdbe11849..670b26d4ca3 100644 --- a/app/models/merge_request_diff_commit.rb +++ b/app/models/merge_request_diff_commit.rb @@ -26,7 +26,7 @@ class MergeRequestDiffCommit < ActiveRecord::Base def to_hash Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash| - hash[key] = public_send(key) + hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 48d00764965..01e0d0155a3 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -149,7 +149,9 @@ class Milestone < ActiveRecord::Base end ## - # Returns the String necessary to reference this Milestone in Markdown + # Returns the String necessary to reference this Milestone in Markdown. Group + # milestones only support name references, and do not support cross-project + # references. # # format - Symbol format to use (default: :iid, optional: :name) # @@ -161,12 +163,16 @@ class Milestone < ActiveRecord::Base # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # def to_reference(from_project = nil, format: :iid, full: false) - return if is_group_milestone? + return if is_group_milestone? && format != :name format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - "#{project.to_reference(from_project, full: full)}#{reference}" + if project + "#{project.to_reference(from_project, full: full)}#{reference}" + else + reference + end end def reference_link_text(from_project = nil) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0bb04194bdb..6073fb94a3f 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -8,6 +8,7 @@ class Namespace < ActiveRecord::Base include Gitlab::VisibilityLevel include Routable include AfterCommitQueue + include Storage::LegacyNamespace # Prevent users from creating unreasonably deep level of nesting. # The number 20 was taken based on maximum nesting level of @@ -41,10 +42,11 @@ class Namespace < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true - after_update :move_dir, if: :path_changed? after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') } - # Save the storage paths before the projects are destroyed to use them on after destroy + # Legacy Storage specific hooks + + after_update :move_dir, if: :path_changed? before_destroy(prepend: true) { prepare_for_destroy } after_destroy :rm_dir @@ -118,43 +120,6 @@ class Namespace < ActiveRecord::Base owner_name end - def move_dir - if any_project_has_container_registry_tags? - raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') - end - - # Move the namespace directory in all storages paths used by member projects - repository_storage_paths.each do |repository_storage_path| - # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, full_path_was) - - unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) - Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" - - # if we cannot move namespace directory we should rollback - # db changes in order to prevent out of sync between db and fs - raise Gitlab::UpdatePathError.new('namespace directory cannot be moved') - end - end - - Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path) - Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path) - - remove_exports! - - # If repositories moved successfully we need to - # send update instructions to users. - # However we cannot allow rollback since we moved namespace dir - # So we basically we mute exceptions in next actions - begin - send_update_instructions - rescue - # Returning false does not rollback after_* transaction but gives - # us information about failing some of tasks - false - end - end - def any_project_has_container_registry_tags? all_projects.any?(&:has_container_registry_tags?) end @@ -206,14 +171,6 @@ class Namespace < ActiveRecord::Base parent_id_changed? end - def prepare_for_destroy - old_repository_storage_paths - end - - def old_repository_storage_paths - @old_repository_storage_paths ||= repository_storage_paths - end - # Includes projects from this namespace and projects from all subgroups # that belongs to this namespace def all_projects @@ -232,37 +189,6 @@ class Namespace < ActiveRecord::Base private - def repository_storage_paths - # We need to get the storage paths for all the projects, even the ones that are - # pending delete. Unscoping also get rids of the default order, which causes - # problems with SELECT DISTINCT. - Project.unscoped do - all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) - end - end - - def rm_dir - # Remove the namespace directory in all storages paths used by member projects - old_repository_storage_paths.each do |repository_storage_path| - # Move namespace directory into trash. - # We will remove it later async - new_path = "#{full_path}+#{id}+deleted" - - if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path) - message = "Namespace directory \"#{full_path}\" moved to \"#{new_path}\"" - Gitlab::AppLogger.info message - - # Remove namespace directroy async with delay so - # GitLab has time to remove all projects first - run_after_commit do - GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) - end - end - end - - remove_exports! - end - def refresh_access_of_projects_invited_groups Group .joins(project_group_links: :project) @@ -270,22 +196,6 @@ class Namespace < ActiveRecord::Base .find_each(&:refresh_members_authorized_projects) end - def remove_exports! - Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) - end - - def export_path - File.join(Gitlab::ImportExport.storage_path, full_path_was) - end - - def full_path_was - if parent - parent.full_path + '/' + path_was - else - path_was - end - end - def nesting_level_allowed if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED errors.add(:parent_id, "has too deep level of nesting") diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 2bc00a082df..0e5acb22d50 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -206,7 +206,7 @@ module Network # Visit branching chains leaves.each do |l| - parents = l.parents(@map).select{|p| p.space.zero?} + parents = l.parents(@map).select {|p| p.space.zero?} parents.each do |p| place_chain(p, l.time) end diff --git a/app/models/note.rb b/app/models/note.rb index d0e3bc0bfed..a752c897d63 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -77,20 +77,20 @@ class Note < ActiveRecord::Base # Scopes scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } - scope :system, ->{ where(system: true) } - scope :user, ->{ where(system: false) } - scope :common, ->{ where(noteable_type: ["", nil]) } - scope :fresh, ->{ order(created_at: :asc, id: :asc) } - scope :updated_after, ->(time){ where('updated_at > ?', time) } - scope :inc_author_project, ->{ includes(:project, :author) } - scope :inc_author, ->{ includes(:author) } + scope :system, -> { where(system: true) } + scope :user, -> { where(system: false) } + scope :common, -> { where(noteable_type: ["", nil]) } + scope :fresh, -> { order(created_at: :asc, id: :asc) } + scope :updated_after, ->(time) { where('updated_at > ?', time) } + scope :inc_author_project, -> { includes(:project, :author) } + scope :inc_author, -> { includes(:author) } scope :inc_relations_for_view, -> do includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata) end - scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) } - scope :new_diff_notes, ->{ where(type: 'DiffNote') } - scope :non_diff_notes, ->{ where(type: ['Note', 'DiscussionNote', nil]) } + scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) } + scope :new_diff_notes, -> { where(type: 'DiffNote') } + scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) } scope :with_associations, -> do # FYI noteable cannot be loaded for LegacyDiffNote for commits diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb new file mode 100644 index 00000000000..418b42d8f1d --- /dev/null +++ b/app/models/notification_recipient.rb @@ -0,0 +1,125 @@ +class NotificationRecipient + attr_reader :user, :type + def initialize( + user, type, + custom_action: nil, + target: nil, + acting_user: nil, + project: nil + ) + @custom_action = custom_action + @acting_user = acting_user + @target = target + @project = project || @target&.project + @user = user + @type = type + end + + def notification_setting + @notification_setting ||= find_notification_setting + end + + def raw_notification_level + notification_setting&.level&.to_sym + end + + def notification_level + # custom is treated the same as watch if it's enabled - otherwise it's + # set to :custom, meaning to send exactly when our type is :participating + # or :mention. + @notification_level ||= + case raw_notification_level + when :custom + if @custom_action && notification_setting&.event_enabled?(@custom_action) + :watch + else + :custom + end + else + raw_notification_level + end + end + + def notifiable? + return false unless has_access? + return false if own_activity? + + return true if @type == :subscription + + return false if notification_level.nil? || notification_level == :disabled + + return %i[participating mention].include?(@type) if notification_level == :custom + + return false if %i[watch participating].include?(notification_level) && excluded_watcher_action? + + return false unless NotificationSetting.levels[notification_level] <= NotificationSetting.levels[@type] + + return false if unsubscribed? + + true + end + + def unsubscribed? + return false unless @target + return false unless @target.respond_to?(:subscriptions) + + subscription = @target.subscriptions.find_by_user_id(@user.id) + subscription && !subscription.subscribed + end + + def own_activity? + return false unless @acting_user + return false if @acting_user.notified_of_own_activity? + + user == @acting_user + end + + def has_access? + DeclarativePolicy.subject_scope do + return false unless user.can?(:receive_notifications) + return false if @project && !user.can?(:read_project, @project) + + return true unless read_ability + return true unless DeclarativePolicy.has_policy?(@target) + + user.can?(read_ability, @target) + end + end + + def excluded_watcher_action? + return false unless @custom_action + return false if raw_notification_level == :custom + + NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action) + end + + private + + def read_ability + return @read_ability if instance_variable_defined?(:@read_ability) + + @read_ability = + case @target + when Issuable + :"read_#{@target.to_ability_name}" + when Ci::Pipeline + :read_build # We have build trace in pipeline emails + when ActiveRecord::Base + :"read_#{@target.class.model_name.name.underscore}" + else + nil + end + end + + def find_notification_setting + project_setting = @project && user.notification_settings_for(@project) + + return project_setting unless project_setting.nil? || project_setting.global? + + group_setting = @project&.group && user.notification_settings_for(@project.group) + + return group_setting unless group_setting.nil? || group_setting.global? + + user.global_notification_setting + end +end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 81844b1e2ca..245f8dddcf9 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,4 +1,8 @@ class NotificationSetting < ActiveRecord::Base + include IgnorableColumn + + ignore_column :events + enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 } default_value_for :level, NotificationSetting.levels[:global] @@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base :success_pipeline ].freeze - store :events, coder: JSON - before_save :convert_events - def self.find_or_create_for(source) setting = find_or_initialize_by(source: source) @@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base setting end - # 1. Check if this event has a value stored in its database column. - # 2. If it does, return that value. - # 3. If it doesn't (the value is nil), return the value from the serialized - # JSON hash in `events`. - (EMAIL_EVENTS - [:failed_pipeline]).each do |event| - define_method(event) do - bool = super() - - bool.nil? ? !!events[event] : bool - end - - alias_method :"#{event}?", event - end - # Allow people to receive failed pipeline notifications if they already have # custom notifications enabled, as these are more like mentions than the other # custom settings. def failed_pipeline bool = super - bool = events[:failed_pipeline] if bool.nil? bool.nil? || bool end alias_method :failed_pipeline?, :failed_pipeline def event_enabled?(event) - respond_to?(event) && public_send(event) - end - - def convert_events - return if events_before_type_cast.nil? - - EMAIL_EVENTS.each do |event| - write_attribute(event, public_send(event)) - end - - write_attribute(:events, nil) + respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/models/project.rb b/app/models/project.rb index d827bfaa806..7010664e1c8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -17,6 +17,7 @@ class Project < ActiveRecord::Base include ProjectFeaturesCompatibility include SelectForProjectAuthorization include Routable + include Storage::LegacyProject extend Gitlab::ConfigHelper @@ -43,9 +44,8 @@ class Project < ActiveRecord::Base default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :only_allow_merge_if_all_discussions_are_resolved, false - after_create :ensure_dir_exist + after_create :ensure_storage_path_exist after_create :create_project_feature, unless: :project_feature - after_save :ensure_dir_exist, if: :namespace_id_changed? after_save :update_project_statistics, if: :namespace_id_changed? # set last_activity_at to the same as created_at @@ -67,10 +67,15 @@ class Project < ActiveRecord::Base after_validation :check_pending_delete + # Legacy Storage specific hooks + + after_save :ensure_storage_path_exist, if: :namespace_id_changed? + acts_as_taggable attr_accessor :new_default_branch attr_accessor :old_path_with_namespace + attr_accessor :template_name attr_writer :pipeline_status alias_attribute :title, :name @@ -159,7 +164,7 @@ class Project < ActiveRecord::Base has_many :todos has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent - has_one :import_data, class_name: 'ProjectImportData' + has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true has_one :project_feature has_one :statistics, class_name: 'ProjectStatistics' @@ -188,6 +193,7 @@ class Project < ActiveRecord::Base accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature + accepts_nested_attributes_for :import_data delegate :name, to: :owner, allow_nil: true, prefix: true delegate :count, to: :forks, prefix: true @@ -375,7 +381,7 @@ class Project < ActiveRecord::Base begin Projects::HousekeepingService.new(project).execute rescue Projects::HousekeepingService::LeaseTaken => e - Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}") + Rails.logger.info("Could not perform housekeeping for project #{project.full_path} (#{project.id}): #{e}") end end end @@ -409,7 +415,7 @@ class Project < ActiveRecord::Base union = Gitlab::SQL::Union.new([projects, namespaces]) - where("projects.id IN (#{union.to_sql})") + where("projects.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection end def search_by_title(query) @@ -476,12 +482,12 @@ class Project < ActiveRecord::Base end def repository - @repository ||= Repository.new(path_with_namespace, self) + @repository ||= Repository.new(full_path, self, disk_path: disk_path) end def container_registry_url if Gitlab.config.registry.enabled - "#{Gitlab.config.registry.host_port}/#{path_with_namespace.downcase}" + "#{Gitlab.config.registry.host_port}/#{full_path.downcase}" end end @@ -520,16 +526,16 @@ class Project < ActiveRecord::Base job_id = if forked? RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path, - forked_from_project.path_with_namespace, + forked_from_project.full_path, self.namespace.full_path) else RepositoryImportWorker.perform_async(self.id) end if job_id - Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}" + Rails.logger.info "Import job started for #{full_path} with job ID #{job_id}" else - Rails.logger.error "Import job failed to start for #{path_with_namespace}" + Rails.logger.error "Import job failed to start for #{full_path}" end end @@ -584,8 +590,6 @@ class Project < ActiveRecord::Base project_import_data.credentials ||= {} project_import_data.credentials = project_import_data.credentials.merge(credentials) end - - project_import_data.save end def import? @@ -690,7 +694,7 @@ class Project < ActiveRecord::Base # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) - path_with_namespace + full_path elsif cross_project_reference?(from) path end @@ -714,7 +718,7 @@ class Project < ActiveRecord::Base author.ensure_incoming_email_token! Gitlab::IncomingEmail.reply_address( - "#{path_with_namespace}+#{author.incoming_email_token}") + "#{full_path}+#{author.incoming_email_token}") end def build_commit_note(commit) @@ -821,7 +825,7 @@ class Project < ActiveRecord::Base if template.nil? # If no template, we should create an instance. Ex `build_gitlab_ci_service` - public_send("build_#{service_name}_service") + public_send("build_#{service_name}_service") # rubocop:disable GitlabSecurity/PublicSend else Service.build_from_template(id, template) end @@ -937,11 +941,11 @@ class Project < ActiveRecord::Base end def repo - repository.raw + repository.rugged end def url_to_repo - gitlab_shell.url_to_repo(path_with_namespace) + gitlab_shell.url_to_repo(full_path) end def repo_exists? @@ -974,56 +978,6 @@ class Project < ActiveRecord::Base !group end - def rename_repo - path_was = previous_changes['path'].first - old_path_with_namespace = File.join(namespace.full_path, path_was) - new_path_with_namespace = File.join(namespace.full_path, path) - - Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}" - - if has_container_registry_tags? - Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!" - - # we currently doesn't support renaming repository if it contains images in container registry - raise StandardError.new('Project cannot be renamed, because images are present in its container registry') - end - - expire_caches_before_rename(old_path_with_namespace) - - if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) - # If repository moved successfully we need to send update instructions to users. - # However we cannot allow rollback since we moved repository - # So we basically we mute exceptions in next actions - begin - gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") - send_move_instructions(old_path_with_namespace) - expires_full_path_cache - - @old_path_with_namespace = old_path_with_namespace - - SystemHooksService.new.execute_hooks_for(self, :rename) - - @repository = nil - rescue => e - Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}" - # Returning false does not rollback after_* transaction but gives - # us information about failing some of tasks - false - end - else - Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" - - # if we cannot move namespace directory we should rollback - # db changes in order to prevent out of sync between db and fs - raise StandardError.new('repository cannot be renamed') - end - - Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" - - Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path) - Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path) - end - # Expires various caches before a project is renamed. def expire_caches_before_rename(old_path) repo = Repository.new(old_path, self) @@ -1048,7 +1002,7 @@ class Project < ActiveRecord::Base git_http_url: http_url_to_repo, namespace: namespace.name, visibility_level: visibility_level, - path_with_namespace: path_with_namespace, + path_with_namespace: full_path, default_branch: default_branch, ci_config_path: ci_config_path } @@ -1092,13 +1046,18 @@ class Project < ActiveRecord::Base end def change_head(branch) - repository.before_change_head - repository.rugged.references.create('HEAD', - "refs/heads/#{branch}", - force: true) - repository.copy_gitattributes(branch) - repository.after_change_head - reload_default_branch + if repository.branch_exists?(branch) + repository.before_change_head + repository.rugged.references.create('HEAD', + "refs/heads/#{branch}", + force: true) + repository.copy_gitattributes(branch) + repository.after_change_head + reload_default_branch + else + errors.add(:base, "Could not change HEAD: branch '#{branch}' does not exist") + false + end end def forked_from?(project) @@ -1109,19 +1068,6 @@ class Project < ActiveRecord::Base merge_requests.where(source_project_id: self.id) end - def create_repository(force: false) - # Forked import is handled asynchronously - return if forked? && !force - - if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) - repository.after_create - true - else - errors.add(:base, 'Failed to create repository via gitlab-shell') - false - end - end - def ensure_repository create_repository(force: true) unless repository_exists? end @@ -1257,7 +1203,7 @@ class Project < ActiveRecord::Base end def pages_path - File.join(Settings.pages.path, path_with_namespace) + File.join(Settings.pages.path, disk_path) end def public_pages_path @@ -1265,9 +1211,21 @@ class Project < ActiveRecord::Base end def remove_private_deploy_keys - deploy_keys.where(public: false).delete_all + exclude_keys_linked_to_other_projects = <<-SQL + NOT EXISTS ( + SELECT 1 + FROM deploy_keys_projects dkp2 + WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id + AND dkp2.project_id != deploy_keys_projects.project_id + ) + SQL + + deploy_keys.where(public: false) + .where(exclude_keys_linked_to_other_projects) + .delete_all end + # TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal? def remove_pages ::Projects::UpdatePagesConfigurationService.new(self).execute @@ -1315,7 +1273,7 @@ class Project < ActiveRecord::Base end def export_path - File.join(Gitlab::ImportExport.storage_path, path_with_namespace) + File.join(Gitlab::ImportExport.storage_path, disk_path) end def export_project_path @@ -1327,16 +1285,12 @@ class Project < ActiveRecord::Base status.zero? end - def ensure_dir_exist - gitlab_shell.add_namespace(repository_storage_path, namespace.full_path) - end - def predefined_variables [ { key: 'CI_PROJECT_ID', value: id.to_s, public: true }, { key: 'CI_PROJECT_NAME', value: path, public: true }, - { key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true }, - { key: 'CI_PROJECT_PATH_SLUG', value: path_with_namespace.parameterize, public: true }, + { key: 'CI_PROJECT_PATH', value: full_path, public: true }, + { key: 'CI_PROJECT_PATH_SLUG', value: full_path.parameterize, public: true }, { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: web_url, public: true } ] @@ -1377,7 +1331,7 @@ class Project < ActiveRecord::Base end def append_or_update_attribute(name, value) - old_values = public_send(name.to_s) + old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any? update_attribute(name, old_values + value) @@ -1441,6 +1395,7 @@ class Project < ActiveRecord::Base alias_method :name_with_namespace, :full_name alias_method :human_name, :full_name + # @deprecated cannot remove yet because it has an index with its name in elasticsearch alias_method :path_with_namespace, :full_path private @@ -1495,7 +1450,7 @@ class Project < ActiveRecord::Base def pending_delete_twin return false unless path - Project.pending_delete.find_by_full_path(path_with_namespace) + Project.pending_delete.find_by_full_path(full_path) end ## diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index c8fabb16dc1..fb1db0255aa 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -55,7 +55,7 @@ class ProjectFeature < ActiveRecord::Base end def access_level(feature) - public_send(ProjectFeature.access_level_attribute(feature)) + public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend end def builds_enabled? @@ -80,7 +80,7 @@ class ProjectFeature < ActiveRecord::Base # which cannot be higher than repository access level def repository_children_level validator = lambda do |field| - level = public_send(field) || ProjectFeature::ENABLED + level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend not_allowed = level > repository_access_level self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed end diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 37730474324..6da6632f4f2 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -1,7 +1,7 @@ require 'carrierwave/orm/activerecord' class ProjectImportData < ActiveRecord::Base - belongs_to :project + belongs_to :project, inverse_of: :import_data attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 2db95b9aaa3..4d23a17a545 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -35,9 +35,9 @@ class FlowdockService < Service data[:after], token: token, repo: project.repository.path_to_repo, - repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", - commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", - diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s" + repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}", + commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/%s", + diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s" ) end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2aa19443198..9ee3a533c1e 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -104,7 +104,7 @@ class JiraService < IssueTrackerService def close_issue(entity, external_issue) issue = jira_request { client.Issue.find(external_issue.iid) } - return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present? + return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present? commit_id = if entity.is_a?(Commit) entity.id @@ -118,7 +118,7 @@ class JiraService < IssueTrackerService # may or may not be allowed. Refresh the issue after transition and check # if it is closed, so we don't have one comment for every commit. issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue) - add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution + add_issue_solved_comment(issue, commit_id, commit_url) if has_resolution?(issue) end def create_cross_reference_note(mentioned, noteable, author) @@ -140,7 +140,7 @@ class JiraService < IssueTrackerService url: resource_url(user_path(author)) }, project: { - name: project.path_with_namespace, + name: project.full_path, url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper }, entity: { @@ -216,6 +216,10 @@ class JiraService < IssueTrackerService end end + def has_resolution?(issue) + issue.respond_to?(:resolution) && issue.resolution.present? + end + def comment_exists?(issue, message) comments = jira_request { issue.comments } diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb index aeaf63abab9..715b215d1db 100644 --- a/app/models/project_statistics.rb +++ b/app/models/project_statistics.rb @@ -14,7 +14,7 @@ class ProjectStatistics < ActiveRecord::Base def refresh!(only: nil) STATISTICS_COLUMNS.each do |column, generator| if only.blank? || only.include?(column) - public_send("update_#{column}") + public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index dfca0031af8..698fdf7a20c 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -1,5 +1,6 @@ class ProjectWiki include Gitlab::ShellAdapter + include Storage::LegacyProjectWiki MARKUPS = { 'Markdown' => :markdown, @@ -26,16 +27,19 @@ class ProjectWiki @project.path + '.wiki' end - def path_with_namespace - @project.path_with_namespace + ".wiki" + def full_path + @project.full_path + '.wiki' end + # @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem + alias_method :path_with_namespace, :full_path + def web_url Gitlab::Routing.url_helpers.project_wiki_url(@project, :home) end def url_to_repo - gitlab_shell.url_to_repo(path_with_namespace) + gitlab_shell.url_to_repo(full_path) end def ssh_url_to_repo @@ -43,11 +47,11 @@ class ProjectWiki end def http_url_to_repo - "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git" + "#{Gitlab.config.gitlab.url}/#{full_path}.git" end def wiki_base_path - [Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('') + [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') end # Returns the Gollum::Wiki object. @@ -109,10 +113,10 @@ class ProjectWiki return false end - def update_page(page, content, format = :markdown, message = nil) + def update_page(page, content:, title: nil, format: :markdown, message: nil) commit = commit_details(:updated, message, page.title) - wiki.update_page(page, page.name, format.to_sym, content, commit) + wiki.update_page(page, title || page.name, format.to_sym, content, commit) update_project_activity end @@ -134,7 +138,7 @@ class ProjectWiki end def repository - @repository ||= Repository.new(path_with_namespace, @project) + @repository ||= Repository.new(full_path, @project, disk_path: disk_path) end def default_branch @@ -142,7 +146,7 @@ class ProjectWiki end def create_repo! - if init_repo(path_with_namespace) + if init_repo(disk_path) wiki = Gollum::Wiki.new(path_to_repo) else raise CouldNotCreateWikiError @@ -162,15 +166,15 @@ class ProjectWiki web_url: web_url, git_ssh_url: ssh_url_to_repo, git_http_url: http_url_to_repo, - path_with_namespace: path_with_namespace, + path_with_namespace: full_path, default_branch: default_branch } end private - def init_repo(path_with_namespace) - gitlab_shell.add_repository(project.repository_storage_path, path_with_namespace) + def init_repo(disk_path) + gitlab_shell.add_repository(project.repository_storage_path, disk_path) end def commit_details(action, message = nil, title = nil) @@ -184,7 +188,7 @@ class ProjectWiki end def path_to_repo - @path_to_repo ||= File.join(project.repository_storage_path, "#{path_with_namespace}.git") + @path_to_repo ||= File.join(project.repository_storage_path, "#{disk_path}.git") end def update_project_activity diff --git a/app/models/repository.rb b/app/models/repository.rb index 50b7a477904..049bebdbe42 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -4,7 +4,7 @@ class Repository include Gitlab::ShellAdapter include RepositoryMirroring - attr_accessor :path_with_namespace, :project + attr_accessor :full_path, :disk_path, :project delegate :ref_name_for_sha, to: :raw_repository @@ -52,21 +52,24 @@ class Repository end end - def initialize(path_with_namespace, project) - @path_with_namespace = path_with_namespace + def initialize(full_path, project, disk_path: nil) + @full_path = full_path + @disk_path = disk_path || full_path @project = project end def raw_repository - return nil unless path_with_namespace + return nil unless full_path @raw_repository ||= initialize_raw_repository end + alias_method :raw, :raw_repository + # Return absolute path to repository def path_to_repo @path_to_repo ||= File.expand_path( - File.join(repository_storage_path, path_with_namespace + ".git") + File.join(repository_storage_path, disk_path + '.git') ) end @@ -129,16 +132,13 @@ class Repository return [] end - ref ||= root_ref - - args = %W( - #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} - --max-count #{limit} --grep=#{query} --regexp-ignore-case - ) - args = args.concat(%W(-- #{path})) if path.present? - - git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines - git_log_results.map { |c| commit(c.chomp) }.compact + raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled| + if is_enabled + find_commits_by_message_by_gitaly(query, ref, path, limit, offset) + else + find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) + end + end end def find_branch(name, fresh_repo: true) @@ -300,7 +300,7 @@ class Repository expire_method_caches(to_refresh) - to_refresh.each { |method| send(method) } + to_refresh.each { |method| send(method) } # rubocop:disable GitlabSecurity/PublicSend end def expire_branch_cache(branch_name = nil) @@ -469,7 +469,7 @@ class Repository # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314 def exists? - return false unless path_with_namespace + return false unless full_path Gitlab::GitalyClient.migrate(:repository_exists) do |enabled| if enabled @@ -612,17 +612,26 @@ class Repository end def last_commit_for_path(sha, path) - sha = last_commit_id_for_path(sha, path) - commit(sha) + raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled| + if is_enabled + last_commit_for_path_by_gitaly(sha, path) + else + last_commit_for_path_by_rugged(sha, path) + end + end end - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383 def last_commit_id_for_path(sha, path) key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}" cache.fetch(key) do - args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path}) - Gitlab::Popen.popen(args, path_to_repo).first.strip + raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled| + if is_enabled + last_commit_for_path_by_gitaly(sha, path).id + else + last_commit_id_for_path_by_shelling_out(sha, path) + end + end end end @@ -677,8 +686,8 @@ class Repository end def refs_contains_sha(ref_type, sha) - args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) - names = Gitlab::Popen.popen(args, path_to_repo).first + args = %W(#{ref_type} --contains #{sha}) + names = run_git(args).first if names.respond_to?(:split) names = names.split("\n").map(&:strip) @@ -756,7 +765,7 @@ class Repository index = Gitlab::Git::Index.new(raw_repository) if start_commit - index.read_tree(start_commit.raw_commit.tree) + index.read_tree(start_commit.rugged_commit.tree) parents = [start_commit.sha] else parents = [] @@ -943,7 +952,7 @@ class Repository if is_enabled raw_repository.is_ancestor?(ancestor_id, descendant_id) else - merge_base_commit(ancestor_id, descendant_id) == ancestor_id + rugged_is_ancestor?(ancestor_id, descendant_id) end end end @@ -956,15 +965,17 @@ class Repository return [] if empty_repo? || query.blank? offset = 2 - args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) - Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) + args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) + + run_git(args).first.scrub.split(/^--$/) end def search_files_by_name(query, ref) return [] if empty_repo? || query.blank? - args = %W(#{Gitlab.config.git.bin_path} ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) - Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip) + args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) + + run_git(args).first.lines.map(&:strip) end def with_repo_branch_commit(start_repository, start_branch_name) @@ -1005,12 +1016,12 @@ class Repository end def fetch_remote(remote, forced: false, no_tags: false) - gitlab_shell.fetch_remote(repository_storage_path, path_with_namespace, remote, forced: forced, no_tags: no_tags) + gitlab_shell.fetch_remote(repository_storage_path, disk_path, remote, forced: forced, no_tags: no_tags) end def fetch_ref(source_path, source_ref, target_ref) - args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) - Gitlab::Popen.popen(args, path_to_repo) + args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) + run_git(args) end def create_ref(ref, ref_path) @@ -1091,6 +1102,12 @@ class Repository private + def run_git(args) + circuit_breaker.perform do + Gitlab::Popen.popen([Gitlab.config.git.bin_path, *args], path_to_repo) + end + end + def blob_data_at(sha, path) blob = blob_at(sha, path) return unless blob @@ -1100,11 +1117,14 @@ class Repository end def refs_directory_exists? - File.exist?(File.join(path_to_repo, 'refs')) + circuit_breaker.perform do + File.exist?(File.join(path_to_repo, 'refs')) + end end def cache - @cache ||= RepositoryCache.new(path_with_namespace, @project.id) + # TODO: should we use UUIDs here? We could move repositories without clearing this cache + @cache ||= RepositoryCache.new(full_path, @project.id) end def tags_sorted_by_committed_date @@ -1127,7 +1147,7 @@ class Repository end def repository_event(event, tags = {}) - Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) + Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags)) end def create_commit(params = {}) @@ -1136,11 +1156,51 @@ class Repository Rugged::Commit.create(rugged, params) end + def last_commit_for_path_by_gitaly(sha, path) + c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path) + commit(c) + end + + def last_commit_for_path_by_rugged(sha, path) + sha = last_commit_id_for_path_by_shelling_out(sha, path) + commit(sha) + end + + def last_commit_id_for_path_by_shelling_out(sha, path) + args = %W(rev-list --max-count=1 #{sha} -- #{path}) + run_git(args).first.strip + end + def repository_storage_path @project.repository_storage_path end def initialize_raw_repository - Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git') + Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git') + end + + def circuit_breaker + @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage) + end + + def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) + ref ||= root_ref + + args = %W( + log #{ref} --pretty=%H --skip #{offset} + --max-count #{limit} --grep=#{query} --regexp-ignore-case + ) + args = args.concat(%W(-- #{path})) if path.present? + + git_log_results = run_git(args).first.lines + + git_log_results.map { |c| commit(c.chomp) }.compact + end + + def find_commits_by_message_by_gitaly(query, ref, path, limit, offset) + raw_repository + .gitaly_commit_client + .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset) + .map { |c| commit(c) } end end diff --git a/app/models/user.rb b/app/models/user.rb index 6e66c587a1f..7935b89662b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,6 +47,11 @@ class User < ActiveRecord::Base devise :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable + # devise overrides #inspect, so we manually use the Referable one + def inspect + referable_inspect + end + # Override Devise::Models::Trackable#update_tracked_fields! # to limit database writes to at most once every hour def update_tracked_fields!(request) @@ -143,6 +148,8 @@ class User < ActiveRecord::Base uniqueness: { case_sensitive: false } validate :namespace_uniq, if: :username_changed? + validate :namespace_move_dir_allowed, if: :username_changed? + validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? @@ -482,6 +489,12 @@ class User < ActiveRecord::Base end end + def namespace_move_dir_allowed + if namespace&.any_project_has_container_registry_tags? + errors.add(:username, 'cannot be changed if a personal project has container registry tags.') + end + end + def avatar_type unless avatar.image? errors.add :avatar, "only images allowed" @@ -523,7 +536,7 @@ class User < ActiveRecord::Base union = Gitlab::SQL::Union .new([groups.select(:id), authorized_projects.select(:namespace_id)]) - Group.where("namespaces.id IN (#{union.to_sql})") + Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection end # Returns a relation of groups the user has access to, including their parent @@ -627,7 +640,11 @@ class User < ActiveRecord::Base end def projects_limit_left - projects_limit - personal_projects.count + projects_limit - personal_projects_count + end + + def personal_projects_count + @personal_projects_count ||= personal_projects.count end def projects_limit_percent @@ -641,16 +658,14 @@ class User < ActiveRecord::Base events = events.where(project_id: project_ids) if project_ids # Use the latest event that has not been pushed or merged recently - events.recent.find do |event| - project = Project.find_by_id(event.project_id) - next unless project - - if project.repository.branch_exists?(event.branch_name) - merge_requests = MergeRequest.where("created_at >= ?", event.created_at) - .where(source_project_id: project.id, - source_branch: event.branch_name) - merge_requests.empty? - end + events.includes(:project).recent.find do |event| + next unless event.project.repository.branch_exists?(event.branch_name) + + merge_requests = MergeRequest.where("created_at >= ?", event.created_at) + .where(source_project_id: event.project.id, + source_branch: event.branch_name) + + merge_requests.empty? end end @@ -712,8 +727,8 @@ class User < ActiveRecord::Base def sanitize_attrs %w[username skype linkedin twitter].each do |attr| - value = public_send(attr) - public_send("#{attr}=", Sanitize.clean(value)) if value.present? + value = public_send(attr) # rubocop:disable GitlabSecurity/PublicSend + public_send("#{attr}=", Sanitize.clean(value)) if value.present? # rubocop:disable GitlabSecurity/PublicSend end end @@ -772,7 +787,7 @@ class User < ActiveRecord::Base def with_defaults User.defaults.each do |k, v| - public_send("#{k}=", v) + public_send("#{k}=", v) # rubocop:disable GitlabSecurity/PublicSend end self @@ -818,7 +833,7 @@ class User < ActiveRecord::Base { name: name, username: username, - avatar_url: avatar_url + avatar_url: avatar_url(only_path: false) } end @@ -912,7 +927,7 @@ class User < ActiveRecord::Base def ci_authorized_runners @ci_authorized_runners ||= begin runner_ids = Ci::RunnerProject - .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})") + .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection .select(:runner_id) Ci::Runner.specific.where(id: runner_ids) end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 148998bc9be..5c7c2204374 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -180,31 +180,50 @@ class WikiPage # # Returns the String SHA1 of the newly created page # or False if the save was unsuccessful. - def create(attr = {}) - @attributes.merge!(attr) + def create(attrs = {}) + @attributes.merge!(attrs) - save :create_page, title, content, format, message + save(page_details: title) do + wiki.create_page(title, content, format, message) + end end # Updates an existing Wiki Page, creating a new version. # - # new_content - The raw markup content to replace the existing. - # format - Optional symbol representing the content format. - # See ProjectWiki::MARKUPS Hash for available formats. - # message - Optional commit message to set on the new version. - # last_commit_sha - Optional last commit sha to validate the page unchanged. + # attrs - Hash of attributes to be updated on the page. + # :content - The raw markup content to replace the existing. + # :format - Optional symbol representing the content format. + # See ProjectWiki::MARKUPS Hash for available formats. + # :message - Optional commit message to set on the new version. + # :last_commit_sha - Optional last commit sha to validate the page unchanged. + # :title - The Title to replace existing title # # Returns the String SHA1 of the newly created page # or False if the save was unsuccessful. - def update(new_content, format: :markdown, message: nil, last_commit_sha: nil) - @attributes[:content] = new_content - @attributes[:format] = format - + def update(attrs = {}) + last_commit_sha = attrs.delete(:last_commit_sha) if last_commit_sha && last_commit_sha != self.last_commit_sha raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.") end - save :update_page, @page, content, format, message + attrs.slice!(:content, :format, :message, :title) + @attributes.merge!(attrs) + page_details = + if title.present? && @page.title != title + title + else + @page.url_path + end + + save(page_details: page_details) do + wiki.update_page( + @page, + content: content, + format: format, + message: attrs[:message], + title: title + ) + end end # Destroys the Wiki Page. @@ -236,30 +255,19 @@ class WikiPage attributes[:format] = @page.format end - def save(method, *args) - saved = false + def save(page_details:) + return unless valid? - project_wiki = wiki - if valid? && project_wiki.send(method, *args) - - page_details = if method == :update_page - # Use url_path instead of path to omit format extension - @page.url_path - else - title - end - - page_title, page_dir = project_wiki.page_title_and_dir(page_details) - gollum_wiki = project_wiki.wiki - @page = gollum_wiki.paged(page_title, page_dir) + unless yield + errors.add(:base, wiki.error_message) + return false + end - set_attributes + page_title, page_dir = wiki.page_title_and_dir(page_details) + gollum_wiki = wiki.wiki + @page = gollum_wiki.paged(page_title, page_dir) - @persisted = true - saved = true - else - errors.add(:base, project_wiki.error_message) if project_wiki.error_message - end - saved + set_attributes + @persisted = errors.blank? end end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 1c91425f589..1be7bbe9953 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy prevent :log_in end - rule { admin | ~restricted_public_level }.policy do + rule { ~(anonymous & restricted_public_level) }.policy do enable :read_users_list end end diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index ad7ad020b03..bdc22d71202 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -35,6 +35,6 @@ class AnalyticsBuildEntity < Grape::Entity private def url_to(route, build, id = nil) - public_send("#{route}_url", build.project.namespace, build.project, id || build) + public_send("#{route}_url", build.project.namespace, build.project, id || build) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb index 44c50f18613..b7d95ea020f 100644 --- a/app/serializers/analytics_issue_entity.rb +++ b/app/serializers/analytics_issue_entity.rb @@ -24,6 +24,6 @@ class AnalyticsIssueEntity < Grape::Entity private def url_to(route, id) - public_send("#{route}_url", request.project.namespace, request.project, id) + public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/blob_entity.rb b/app/serializers/blob_entity.rb new file mode 100644 index 00000000000..56f173e5a27 --- /dev/null +++ b/app/serializers/blob_entity.rb @@ -0,0 +1,17 @@ +class BlobEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :last_commit do |blob| + request.project.repository.last_commit_for_path(blob.commit_id, blob.path) + end + + expose :icon do |blob| + IconsHelper.file_type_icon_class('file', blob.mode, blob.name) + end + + expose :url do |blob| + project_blob_path(request.project, File.join(request.ref, blob.path)) + end +end diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index d6de43bcbcb..72e56a2c77f 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -46,6 +46,6 @@ class JobEntity < Grape::Entity end def path_to(route, build) - send("#{route}_path", build.project.namespace, build.project, build) + send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 7f17f2bf604..07650ce6f20 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -2,7 +2,6 @@ class MergeRequestEntity < IssuableEntity include RequestAwareEntity expose :in_progress_merge_commit_sha - expose :locked_at expose :merge_commit_sha expose :merge_error expose :merge_params @@ -32,6 +31,7 @@ class MergeRequestEntity < IssuableEntity expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline # Booleans + expose :merge_ongoing?, as: :merge_ongoing expose :work_in_progress?, as: :work_in_progress expose :source_branch_exists?, as: :source_branch_exists expose :mergeable_discussions_state?, as: :mergeable_discussions_state diff --git a/app/serializers/submodule_entity.rb b/app/serializers/submodule_entity.rb new file mode 100644 index 00000000000..9a7eb5e7880 --- /dev/null +++ b/app/serializers/submodule_entity.rb @@ -0,0 +1,23 @@ +class SubmoduleEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :icon do |blob| + 'archive' + end + + expose :project_url do |blob| + submodule_links(blob, request).first + end + + expose :tree_url do |blob| + submodule_links(blob, request).last + end + + private + + def submodule_links(blob, request) + @submodule_links ||= SubmoduleHelper.submodule_links(blob, request.ref, request.repository) + end +end diff --git a/app/serializers/tree_entity.rb b/app/serializers/tree_entity.rb new file mode 100644 index 00000000000..555e5cf83bd --- /dev/null +++ b/app/serializers/tree_entity.rb @@ -0,0 +1,17 @@ +class TreeEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :last_commit do |tree| + request.project.repository.last_commit_for_path(tree.commit_id, tree.path) + end + + expose :icon do |tree| + IconsHelper.file_type_icon_class('folder', tree.mode, tree.name) + end + + expose :url do |tree| + project_tree_path(request.project, File.join(request.ref, tree.path)) + end +end diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb new file mode 100644 index 00000000000..23b65aa4a4c --- /dev/null +++ b/app/serializers/tree_root_entity.rb @@ -0,0 +1,8 @@ +# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`. +class TreeRootEntity < Grape::Entity + expose :path + + expose :trees, using: TreeEntity + expose :blobs, using: BlobEntity + expose :submodules, using: SubmoduleEntity +end diff --git a/app/serializers/tree_serializer.rb b/app/serializers/tree_serializer.rb new file mode 100644 index 00000000000..713ade23bc9 --- /dev/null +++ b/app/serializers/tree_serializer.rb @@ -0,0 +1,3 @@ +class TreeSerializer < BaseSerializer + entity TreeRootEntity +end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 5e151b0f044..7dae5880931 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -103,6 +103,8 @@ module Auth build_can_pull?(requested_project) || user_can_pull?(requested_project) when 'push' build_can_push?(requested_project) || user_can_push?(requested_project) + when '*' + user_can_admin?(requested_project) else false end @@ -120,6 +122,11 @@ module Auth (requested_project == project || can?(current_user, :build_read_container_image, requested_project)) end + def user_can_admin?(requested_project) + has_authentication_ability?(:admin_container_image) && + can?(current_user, :admin_container_image, requested_project) + end + def user_can_pull?(requested_project) has_authentication_ability?(:read_container_image) && can?(current_user, :read_container_image, requested_project) diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index b951e8d0c9f..fc87bd6a659 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -30,6 +30,7 @@ module Ci # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. build.runner_id = runner.id build.run! + register_success(build) return Result.new(build, true) rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError @@ -46,6 +47,7 @@ module Ci end end + register_failure Result.new(nil, valid) end @@ -81,5 +83,27 @@ module Ci def shared_runner_build_limits_feature_enabled? ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true' end + + def register_failure + failed_attempt_counter.increase + attempt_counter.increase + end + + def register_success(job) + job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at) + attempt_counter.increase + end + + def failed_attempt_counter + @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job") + end + + def attempt_counter + @attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_total, "Counts the times a runner tries to register a job") + end + + def job_queue_duration_seconds + @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time') + end end end diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index 5c9e2a16c71..ff11bd59d29 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -11,7 +11,7 @@ class DeleteMergedBranchesService < BaseService # Prevent deletion of branches relevant to open merge requests branches -= merge_request_branch_names # Prevent deletion of protected branches - branches -= project.protected_branches.pluck(:name) + branches = branches.reject { |branch| project.protected_for?(branch) } branches.each do |branch| DeleteBranchService.new(project, current_user).execute(branch) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 32925e9c1f2..545ca0742e4 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -60,7 +60,7 @@ class GitOperationService start_branch_name = nil if start_repository.empty_repo? if start_branch_name && !start_repository.branch_exists?(start_branch_name) - raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.path_with_namespace}" + raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}" end update_branch_with_hooks(branch_name) do diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ea497729115..b84a6fd2b7d 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -2,11 +2,8 @@ class IssuableBaseService < BaseService private def create_milestone_note(issuable) - milestone = issuable.milestone - return if milestone && milestone.is_group_milestone? - SystemNoteService.change_milestone( - issuable, issuable.project, current_user, milestone) + issuable, issuable.project, current_user, issuable.milestone) end def create_labels_note(issuable, old_labels) @@ -182,7 +179,6 @@ class IssuableBaseService < BaseService if params.present? && create_issuable(issuable, params, label_ids: label_ids) after_create(issuable) - issuable.create_cross_references!(current_user) execute_hooks(issuable) invalidate_cache_counts(issuable, users: issuable.assignees) end @@ -288,7 +284,7 @@ class IssuableBaseService < BaseService todo_service.mark_todo(issuable, current_user) when 'done' todo = TodosFinder.new(current_user).execute.find_by(target: issuable) - todo_service.mark_todos_as_done([todo], current_user) if todo + todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo end end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 718a7ac1f22..234fcbede03 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -15,11 +15,15 @@ module Issues def before_create(issue) spam_check(issue, current_user) issue.move_to_end + + # current_user (defined in BaseService) is not available within run_after_commit block + user = current_user + issue.run_after_commit do + NewIssueWorker.perform_async(issue.id, user.id) + end end def after_create(issuable) - event_service.open_issue(issuable, current_user) - notification_service.new_issue(issuable, current_user) todo_service.new_issue(issuable, current_user) user_agent_detail_service.create resolve_discussions_with_issue(issuable) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index d2ece354efc..775efed48eb 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -37,7 +37,7 @@ module Labels union = Gitlab::SQL::Union.new(label_ids) - Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq + Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq # rubocop:disable GitlabSecurity/SqlInjection end def group_labels_applied_to_issues diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 19189e64acf..fa0c0b7175c 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -12,20 +12,32 @@ module MergeRequests merge_request.source_project = source_project merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - merge_request.head_pipeline = head_pipeline_for(merge_request) create(merge_request) end + def before_create(merge_request) + # current_user (defined in BaseService) is not available within run_after_commit block + user = current_user + merge_request.run_after_commit do + NewMergeRequestWorker.perform_async(merge_request.id, user.id) + end + end + def after_create(issuable) event_service.open_mr(issuable, current_user) - notification_service.new_merge_request(issuable, current_user) todo_service.new_merge_request(issuable, current_user) issuable.cache_merge_request_closes_issues!(current_user) + update_merge_requests_head_pipeline(issuable) end private + def update_merge_requests_head_pipeline(merge_request) + pipeline = head_pipeline_for(merge_request) + merge_request.update(head_pipeline_id: pipeline.id) if pipeline + end + def head_pipeline_for(merge_request) return unless merge_request.source_project diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 9ac561e4bd2..21c9c314a2a 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -1,331 +1,288 @@ # # Used by NotificationService to determine who should receive notification # -class NotificationRecipientService - attr_reader :project - - def initialize(project) - @project = project +module NotificationRecipientService + def self.notifiable_users(users, *args) + users.compact.map { |u| NotificationRecipient.new(u, *args) }.select(&:notifiable?).map(&:user) end - def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true) - custom_action = build_custom_key(action, target) - - recipients = participants(target, current_user) - recipients = add_project_watchers(recipients) - recipients = add_custom_notifications(recipients, custom_action) - recipients = reject_mention_users(recipients) - - # Re-assign is considered as a mention of the new assignee so we add the - # new assignee to the list of recipients after we rejected users with - # the "on mention" notification level - case custom_action - when :reassign_merge_request - recipients << previous_assignee if previous_assignee - recipients << target.assignee - when :reassign_issue - previous_assignees = Array(previous_assignee) - recipients.concat(previous_assignees) - recipients.concat(target.assignees) - end - - recipients = reject_muted_users(recipients) - recipients = add_subscribed_users(recipients, target) - - if [:new_issue, :new_merge_request].include?(custom_action) - recipients = add_labels_subscribers(recipients, target) - end - - recipients = reject_unsubscribed_users(recipients, target) - recipients = reject_users_without_access(recipients, target) + def self.notifiable?(user, *args) + NotificationRecipient.new(user, *args).notifiable? + end - recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity? + def self.build_recipients(*a) + Builder::Default.new(*a).recipient_users + end - recipients.uniq + def self.build_new_note_recipients(*a) + Builder::NewNote.new(*a).recipient_users end - def build_pipeline_recipients(target, current_user, action:) - return [] unless current_user + module Builder + class Base + def initialize(*) + raise 'abstract' + end - custom_action = - case action.to_s - when 'failed' - :failed_pipeline - when 'success' - :success_pipeline + def build! + raise 'abstract' end - notification_setting = notification_setting_for_user_project(current_user, target.project) + def filter! + recipients.select!(&:notifiable?) + end - return [] if notification_setting.mention? || notification_setting.disabled? + def acting_user + current_user + end - return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action) + def target + raise 'abstract' + end - return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action) + # rubocop:disable Rails/Delegate + def project + target.project + end - reject_users_without_access([current_user], target) - end + def recipients + @recipients ||= [] + end - def build_relabeled_recipients(target, current_user, labels:) - recipients = add_labels_subscribers([], target, labels: labels) - recipients = reject_unsubscribed_users(recipients, target) - recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) unless current_user.notified_of_own_activity? - recipients.uniq - end + def <<(pair) + users, type = pair - def build_new_note_recipients(note) - target = note.noteable + if users.is_a?(ActiveRecord::Relation) + users = users.includes(:notification_settings) + end - ability, subject = if note.for_personal_snippet? - [:read_personal_snippet, note.noteable] - else - [:read_project, note.project] - end + users = Array(users) + users.compact! + recipients.concat(users.map { |u| make_recipient(u, type) }) + end - mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) } + def user_scope + User.includes(:notification_settings) + end - # Add all users participating in the thread (author, assignee, comment authors) - recipients = participants(target, note.author) || mentioned_users + def make_recipient(user, type) + NotificationRecipient.new( + user, type, + project: project, + custom_action: custom_action, + target: target, + acting_user: acting_user + ) + end - unless note.for_personal_snippet? - # Merge project watchers - recipients = add_project_watchers(recipients) + def recipient_users + @recipient_users ||= + begin + build! + filter! + users = recipients.map(&:user) + users.uniq! + users.freeze + end + end - # Merge project with custom notification - recipients = add_custom_notifications(recipients, :new_note) - end + def custom_action + nil + end - # Reject users with Mention notification level, except those mentioned in _this_ note. - recipients = reject_mention_users(recipients - mentioned_users) - recipients = recipients + mentioned_users + protected - recipients = reject_muted_users(recipients) + def add_participants(user) + return unless target.respond_to?(:participants) - recipients = add_subscribed_users(recipients, note.noteable) - recipients = reject_unsubscribed_users(recipients, note.noteable) - recipients = reject_users_without_access(recipients, note.noteable) + self << [target.participants(user), :watch] + end - recipients.delete(note.author) unless note.author.notified_of_own_activity? - recipients.uniq - end + # Get project/group users with CUSTOM notification level + def add_custom_notifications + user_ids = [] - # Remove users with disabled notifications from array - # Also remove duplications and nil recipients - def reject_muted_users(users) - reject_users(users, :disabled) - end + # Users with a notification setting on group or project + user_ids += user_ids_notifiable_on(project, :custom) + user_ids += user_ids_notifiable_on(project.group, :custom) - protected + # Users with global level custom + user_ids_with_project_level_global = user_ids_notifiable_on(project, :global) + user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global) - # Ensure that if we modify this array, we aren't modifying the memoised - # participants on the target. - def participants(target, user) - return unless target.respond_to?(:participants) + global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global) + user_ids += user_ids_with_global_level_custom(global_users_ids, custom_action) - target.participants(user).dup - end + self << [user_scope.where(id: user_ids), :watch] + end - # Get project/group users with CUSTOM notification level - def add_custom_notifications(recipients, action) - user_ids = [] + def add_project_watchers + self << [project_watchers, :watch] + end - # Users with a notification setting on group or project - user_ids += user_ids_notifiable_on(project, :custom, action) - user_ids += user_ids_notifiable_on(project.group, :custom, action) + # Get project users with WATCH notification level + def project_watchers + project_members_ids = user_ids_notifiable_on(project) - # Users with global level custom - user_ids_with_project_level_global = user_ids_notifiable_on(project, :global) - user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global) + user_ids_with_project_global = user_ids_notifiable_on(project, :global) + user_ids_with_group_global = user_ids_notifiable_on(project.group, :global) - global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global) - user_ids += user_ids_with_global_level_custom(global_users_ids, action) + user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq) - recipients.concat(User.find(user_ids)) - end + user_ids_with_project_setting = select_project_members_ids(user_ids_with_project_global, user_ids) + user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids) - def add_project_watchers(recipients) - recipients.concat(project_watchers).compact - end + user_scope.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq) + end - # Get project users with WATCH notification level - def project_watchers - project_members_ids = user_ids_notifiable_on(project) + def add_subscribed_users + return unless target.respond_to? :subscribers - user_ids_with_project_global = user_ids_notifiable_on(project, :global) - user_ids_with_group_global = user_ids_notifiable_on(project.group, :global) + self << [target.subscribers(project), :subscription] + end - user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq) + def user_ids_notifiable_on(resource, notification_level = nil) + return [] unless resource - user_ids_with_project_setting = select_project_members_ids(project, user_ids_with_project_global, user_ids) - user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids) + scope = resource.notification_settings - User.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq).to_a - end + if notification_level + scope = scope.where(level: NotificationSetting.levels[notification_level]) + end - # Remove users with notification level 'Mentioned' - def reject_mention_users(users) - reject_users(users, :mention) - end + scope.pluck(:user_id) + end - def add_subscribed_users(recipients, target) - return recipients unless target.respond_to? :subscribers + # Build a list of user_ids based on project notification settings + def select_project_members_ids(global_setting, user_ids_global_level_watch) + user_ids = user_ids_notifiable_on(project, :watch) - recipients + target.subscribers(project) - end + # If project setting is global, add to watch list if global setting is watch + user_ids + (global_setting & user_ids_global_level_watch) + end - def user_ids_notifiable_on(resource, notification_level = nil, action = nil) - return [] unless resource + # Build a list of user_ids based on group notification settings + def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch) + uids = user_ids_notifiable_on(group, :watch) - if notification_level - settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level]) - settings = settings.select { |setting| setting.event_enabled?(action) } if action.present? - settings.map(&:user_id) - else - resource.notification_settings.pluck(:user_id) - end - end + # Group setting is global, add to user_ids list if global setting is watch + uids + (global_setting & user_ids_global_level_watch) - project_members + end - # Build a list of user_ids based on project notification settings - def select_project_members_ids(project, global_setting, user_ids_global_level_watch) - user_ids = user_ids_notifiable_on(project, :watch) + def user_ids_with_global_level_watch(ids) + settings_with_global_level_of(:watch, ids).pluck(:user_id) + end - # If project setting is global, add to watch list if global setting is watch - global_setting.each do |user_id| - if user_ids_global_level_watch.include?(user_id) - user_ids << user_id + def user_ids_with_global_level_custom(ids, action) + settings_with_global_level_of(:custom, ids).pluck(:user_id) end - end - user_ids - end + def settings_with_global_level_of(level, ids) + NotificationSetting.where( + user_id: ids, + source_type: nil, + level: NotificationSetting.levels[level] + ) + end - # Build a list of user_ids based on group notification settings - def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch) - uids = user_ids_notifiable_on(group, :watch) + def add_labels_subscribers(labels: nil) + return unless target.respond_to? :labels - # Group setting is watch, add to user_ids list if user is not project member - user_ids = [] - uids.each do |user_id| - if project_members.exclude?(user_id) - user_ids << user_id + (labels || target.labels).each do |label| + self << [label.subscribers(project), :subscription] + end end end - # Group setting is global, add to user_ids list if global setting is watch - global_setting.each do |user_id| - if project_members.exclude?(user_id) && user_ids_global_level_watch.include?(user_id) - user_ids << user_id + class Default < Base + attr_reader :target + attr_reader :current_user + attr_reader :action + attr_reader :previous_assignee + attr_reader :skip_current_user + def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true) + @target = target + @current_user = current_user + @action = action + @previous_assignee = previous_assignee + @skip_current_user = skip_current_user end - end - - user_ids - end - - def user_ids_with_global_level_watch(ids) - settings_with_global_level_of(:watch, ids).pluck(:user_id) - end - - def user_ids_with_global_level_custom(ids, action) - settings = settings_with_global_level_of(:custom, ids) - settings = settings.select { |setting| setting.event_enabled?(action) } - settings.map(&:user_id) - end - def settings_with_global_level_of(level, ids) - NotificationSetting.where( - user_id: ids, - source_type: nil, - level: NotificationSetting.levels[level] - ) - end + def build! + add_participants(current_user) + add_project_watchers + add_custom_notifications + + # Re-assign is considered as a mention of the new assignee + case custom_action + when :reassign_merge_request + self << [previous_assignee, :mention] + self << [target.assignee, :mention] + when :reassign_issue + previous_assignees = Array(previous_assignee) + self << [previous_assignees, :mention] + self << [target.assignees, :mention] + end + + add_subscribed_users + + if [:new_issue, :new_merge_request].include?(custom_action) + add_labels_subscribers + end + end - # Reject users which has certain notification level - # - # Example: - # reject_users(users, :watch, project) - # - def reject_users(users, level) - level = level.to_s + def acting_user + current_user if skip_current_user + end - unless NotificationSetting.levels.keys.include?(level) - raise 'Invalid notification level' + # Build event key to search on custom notification level + # Check NotificationSetting::EMAIL_EVENTS + def custom_action + @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym + end end - users = users.to_a.compact.uniq - users = users.select { |u| u.can?(:receive_notifications) } - - users.reject do |user| - global_notification_setting = user.global_notification_setting - - next global_notification_setting.level == level unless project - - setting = user.notification_settings_for(project) - - if project.group && (setting.nil? || setting.global?) - setting = user.notification_settings_for(project.group) + class NewNote < Base + attr_reader :note + def initialize(note) + @note = note end - # reject users who globally set mention notification and has no setting per project/group - next global_notification_setting.level == level unless setting - - # reject users who set mention notification in project - next true if setting.level == level - - # reject users who have mention level in project and disabled in global settings - setting.global? && global_notification_setting.level == level - end - end + def target + note.noteable + end - def reject_unsubscribed_users(recipients, target) - return recipients unless target.respond_to? :subscriptions + # NOTE: may be nil, in the case of a PersonalSnippet + # + # (this is okay because NotificationRecipient is written + # to handle nil projects) + def project + note.project + end - recipients.reject do |user| - subscription = target.subscriptions.find_by_user_id(user.id) - subscription && !subscription.subscribed - end - end + def build! + # Add all users participating in the thread (author, assignee, comment authors) + add_participants(note.author) + self << [note.mentioned_users, :mention] - def reject_users_without_access(recipients, target) - ability = case target - when Issuable - :"read_#{target.to_ability_name}" - when Ci::Pipeline - :read_build # We have build trace in pipeline emails - end + unless note.for_personal_snippet? + # Merge project watchers + add_project_watchers - return recipients unless ability + # Merge project with custom notification + add_custom_notifications + end - recipients.select do |user| - user.can?(ability, target) - end - end + add_subscribed_users + end - def add_labels_subscribers(recipients, target, labels: nil) - return recipients unless target.respond_to? :labels + def custom_action + :new_note + end - (labels || target.labels).each do |label| - recipients += label.subscribers(project) + def acting_user + note.author + end end - - recipients - end - - # Build event key to search on custom notification level - # Check NotificationSetting::EMAIL_EVENTS - def build_custom_key(action, object) - "#{action}_#{object.class.model_name.name.underscore}".to_sym - end - - def notification_setting_for_user_project(user, project) - project_setting = user.notification_settings_for(project) - - return project_setting unless project_setting.global? - - group_setting = user.notification_settings_for(project.group) - - return group_setting unless group_setting.global? - - user.global_notification_setting end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index b94921d2a08..df04b1a4fe3 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -42,7 +42,7 @@ class NotificationService # * users with custom level checked with "new issue" # def new_issue(issue, current_user) - new_resource_email(issue, issue.project, :new_issue_email) + new_resource_email(issue, :new_issue_email) end # When issue text is updated, we should send an email to: @@ -52,7 +52,6 @@ class NotificationService def new_mentions_in_issue(issue, new_mentioned_users, current_user) new_mentions_in_resource_email( issue, - issue.project, new_mentioned_users, current_user, :new_mention_in_issue_email @@ -67,7 +66,7 @@ class NotificationService # * users with custom level checked with "close issue" # def close_issue(issue, current_user) - close_resource_email(issue, issue.project, current_user, :closed_issue_email) + close_resource_email(issue, current_user, :closed_issue_email) end # When we reassign an issue we should send an email to: @@ -77,7 +76,7 @@ class NotificationService # * users with custom level checked with "reassign issue" # def reassigned_issue(issue, current_user, previous_assignees = []) - recipients = NotificationRecipientService.new(issue.project).build_recipients( + recipients = NotificationRecipientService.build_recipients( issue, current_user, action: "reassign", @@ -102,7 +101,7 @@ class NotificationService # * watchers of the issue's labels # def relabeled_issue(issue, added_labels, current_user) - relabeled_resource_email(issue, issue.project, added_labels, current_user, :relabeled_issue_email) + relabeled_resource_email(issue, added_labels, current_user, :relabeled_issue_email) end # When create a merge request we should send an email to: @@ -113,7 +112,7 @@ class NotificationService # * users with custom level checked with "new merge request" # def new_merge_request(merge_request, current_user) - new_resource_email(merge_request, merge_request.target_project, :new_merge_request_email) + new_resource_email(merge_request, :new_merge_request_email) end # When merge request text is updated, we should send an email to: @@ -123,7 +122,6 @@ class NotificationService def new_mentions_in_merge_request(merge_request, new_mentioned_users, current_user) new_mentions_in_resource_email( merge_request, - merge_request.target_project, new_mentioned_users, current_user, :new_mention_in_merge_request_email @@ -137,7 +135,7 @@ class NotificationService # * users with custom level checked with "reassign merge request" # def reassigned_merge_request(merge_request, current_user) - reassign_resource_email(merge_request, merge_request.target_project, current_user, :reassigned_merge_request_email) + reassign_resource_email(merge_request, current_user, :reassigned_merge_request_email) end # When we add labels to a merge request we should send an email to: @@ -145,21 +143,20 @@ class NotificationService # * watchers of the mr's labels # def relabeled_merge_request(merge_request, added_labels, current_user) - relabeled_resource_email(merge_request, merge_request.target_project, added_labels, current_user, :relabeled_merge_request_email) + relabeled_resource_email(merge_request, added_labels, current_user, :relabeled_merge_request_email) end def close_mr(merge_request, current_user) - close_resource_email(merge_request, merge_request.target_project, current_user, :closed_merge_request_email) + close_resource_email(merge_request, current_user, :closed_merge_request_email) end def reopen_issue(issue, current_user) - reopen_resource_email(issue, issue.project, current_user, :issue_status_changed_email, 'reopened') + reopen_resource_email(issue, current_user, :issue_status_changed_email, 'reopened') end def merge_mr(merge_request, current_user) close_resource_email( merge_request, - merge_request.target_project, current_user, :merged_merge_request_email, skip_current_user: !merge_request.merge_when_pipeline_succeeds? @@ -169,7 +166,6 @@ class NotificationService def reopen_mr(merge_request, current_user) reopen_resource_email( merge_request, - merge_request.target_project, current_user, :merge_request_status_email, 'reopened' @@ -177,7 +173,7 @@ class NotificationService end def resolve_all_discussions(merge_request, current_user) - recipients = NotificationRecipientService.new(merge_request.target_project).build_recipients( + recipients = NotificationRecipientService.build_recipients( merge_request, current_user, action: "resolve_all_discussions") @@ -202,7 +198,7 @@ class NotificationService notify_method = "note_#{note.to_ability_name}_email".to_sym - recipients = NotificationRecipientService.new(note.project).build_new_note_recipients(note) + recipients = NotificationRecipientService.build_new_note_recipients(note) recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later end @@ -270,8 +266,7 @@ class NotificationService end def project_was_moved(project, old_path_with_namespace) - recipients = project.team.members - recipients = NotificationRecipientService.new(project).reject_muted_users(recipients) + recipients = NotificationRecipientService.notifiable_users(project.team.members, :mention, project: project) recipients.each do |recipient| mailer.project_was_moved_email( @@ -283,7 +278,7 @@ class NotificationService end def issue_moved(issue, new_issue, current_user) - recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user, action: 'moved') + recipients = NotificationRecipientService.build_recipients(issue, current_user, action: 'moved') recipients.map do |recipient| email = mailer.issue_moved_email(recipient, issue, new_issue, current_user) @@ -305,10 +300,10 @@ class NotificationService return unless mailer.respond_to?(email_template) - recipients ||= NotificationRecipientService.new(pipeline.project).build_pipeline_recipients( - pipeline, - pipeline.user, - action: pipeline.status + recipients ||= NotificationRecipientService.notifiable_users( + [pipeline.user], :watch, + custom_action: :"#{pipeline.status}_pipeline", + target: pipeline ).map(&:notification_email) if recipients.any? @@ -318,16 +313,16 @@ class NotificationService protected - def new_resource_email(target, project, method) - recipients = NotificationRecipientService.new(project).build_recipients(target, target.author, action: "new") + def new_resource_email(target, method) + recipients = NotificationRecipientService.build_recipients(target, target.author, action: "new") recipients.each do |recipient| mailer.send(method, recipient.id, target.id).deliver_later end end - def new_mentions_in_resource_email(target, project, new_mentioned_users, current_user, method) - recipients = NotificationRecipientService.new(project).build_recipients(target, current_user, action: "new") + def new_mentions_in_resource_email(target, new_mentioned_users, current_user, method) + recipients = NotificationRecipientService.build_recipients(target, current_user, action: "new") recipients = recipients & new_mentioned_users recipients.each do |recipient| @@ -335,10 +330,10 @@ class NotificationService end end - def close_resource_email(target, project, current_user, method, skip_current_user: true) + def close_resource_email(target, current_user, method, skip_current_user: true) action = method == :merged_merge_request_email ? "merge" : "close" - recipients = NotificationRecipientService.new(project).build_recipients( + recipients = NotificationRecipientService.build_recipients( target, current_user, action: action, @@ -350,11 +345,11 @@ class NotificationService end end - def reassign_resource_email(target, project, current_user, method) + def reassign_resource_email(target, current_user, method) previous_assignee_id = previous_record(target, 'assignee_id') previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id - recipients = NotificationRecipientService.new(project).build_recipients( + recipients = NotificationRecipientService.build_recipients( target, current_user, action: "reassign", @@ -372,8 +367,14 @@ class NotificationService end end - def relabeled_resource_email(target, project, labels, current_user, method) - recipients = NotificationRecipientService.new(project).build_relabeled_recipients(target, current_user, labels: labels) + def relabeled_resource_email(target, labels, current_user, method) + recipients = labels.flat_map { |l| l.subscribers(target.project) } + recipients = NotificationRecipientService.notifiable_users( + recipients, :subscription, + target: target, + acting_user: current_user + ) + label_names = labels.map(&:name) recipients.each do |recipient| @@ -381,8 +382,8 @@ class NotificationService end end - def reopen_resource_email(target, project, current_user, method, status) - recipients = NotificationRecipientService.new(project).build_recipients(target, current_user, action: "reopen") + def reopen_resource_email(target, current_user, method, status) + recipients = NotificationRecipientService.build_recipients(target, current_user, action: "reopen") recipients.each do |recipient| mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index fc85f398935..724a77c873a 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -5,7 +5,15 @@ module Projects end def milestones - @project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title]) + finder_params = { + project_ids: [@project.id], + state: :active, + order: { due_date: :asc, title: :asc } + } + + finder_params[:group_ids] = [@project.group.id] if @project.group + + MilestonesFinder.new(finder_params).execute.select([:iid, :title]) end def merge_requests diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb new file mode 100644 index 00000000000..87d9ed7a0e6 --- /dev/null +++ b/app/services/projects/create_from_template_service.rb @@ -0,0 +1,15 @@ +module Projects + class CreateFromTemplateService < BaseService + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + params[:file] = Gitlab::ProjectTemplate.find(params[:template_name]).file + + GitlabProjectsImportService.new(@current_user, @params).execute + ensure + params[:file]&.close + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index e874a2d8789..48578b6d9e5 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -5,6 +5,10 @@ module Projects end def execute + if @params[:template_name]&.present? + return ::Projects::CreateFromTemplateService.new(current_user, params).execute + end + forked_from_project_id = params.delete(:forked_from_project_id) import_data = params.delete(:import_data) @skip_wiki = params.delete(:skip_wiki) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index f6e8b6655f2..11ad4838471 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -9,7 +9,7 @@ module Projects def async_execute project.update_attribute(:pending_delete, true) job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params) - Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.path_with_namespace} with job ID #{job_id}") + Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}") end def execute @@ -40,7 +40,7 @@ module Projects private def repo_path - project.path_with_namespace + project.disk_path end def wiki_path @@ -127,7 +127,7 @@ module Projects def flush_caches(project) project.repository.before_delete - Repository.new(wiki_path, project).before_delete + Repository.new(wiki_path, project, disk_path: repo_path).before_delete end end end diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb new file mode 100644 index 00000000000..4ca6414b73b --- /dev/null +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -0,0 +1,36 @@ +# This service is an adapter used to for the GitLab Import feature, and +# creating a project from a template. +# The latter will under the hood just import an archive supplied by GitLab. +module Projects + class GitlabProjectsImportService + attr_reader :current_user, :params + + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + FileUtils.mkdir_p(File.dirname(import_upload_path)) + FileUtils.copy_entry(file.path, import_upload_path) + + Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id], + current_user, + import_upload_path, + params[:path]).execute + end + + private + + def import_upload_path + @import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename) + end + + def tmp_filename + "#{SecureRandom.hex}_#{params[:path]}" + end + + def file + params[:file] + end + end +end diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 535da706159..fe4e8ea10bf 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -2,7 +2,7 @@ module Projects module ImportExport class ExportService < BaseService def execute(_options = {}) - @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work')) + @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.disk_path, 'work')) save_all end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index eea17e24903..c3bf0031409 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -11,7 +11,7 @@ module Projects success rescue => e - error("Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}") + error("Error importing repository #{project.import_url} into #{project.full_path} - #{e.message}") end private @@ -34,8 +34,12 @@ module Projects def import_repository raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url) + # We should return early for a GitHub import because the new GitHub + # importer fetch the project repositories for us. + return if project.github_import? + begin - if project.github_import? || project.gitea_import? + if project.gitea_import? fetch_repository else clone_repository @@ -51,11 +55,11 @@ module Projects end def clone_repository - gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) + gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url) end def fetch_repository - project.create_repository + project.ensure_repository project.repository.add_remote(project.import_type, project.import_url) project.repository.set_remote_as_mirror(project.import_type) project.repository.fetch_remote(project.import_type, forced: true) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 4bb98e5cb4e..5957f612e84 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -34,7 +34,7 @@ module Projects private def transfer(project) - @old_path = project.path_with_namespace + @old_path = project.full_path @old_group = project.group @new_path = File.join(@new_namespace.try(:full_path) || '', project.path) @old_namespace = project.namespace @@ -61,11 +61,13 @@ module Projects project.send_move_instructions(@old_path) # Move main repository + # TODO: check storage type and NOOP when not using Legacy unless move_repo_folder(@old_path, @new_path) raise TransferError.new('Cannot move project') end # Move wiki repo also if present + # TODO: check storage type and NOOP when not using Legacy move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki") # Move missing group labels to project diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 749a1cc56d8..5038155ca31 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -33,8 +33,10 @@ module Projects success end rescue => e + register_failure error(e.message) ensure + register_attempt build.erase_artifacts! unless build.has_expiring_artifacts? end @@ -168,5 +170,21 @@ module Projects def sha build.sha end + + def register_attempt + pages_deployments_total_counter.increase + end + + def register_failure + pages_deployments_failed_total_counter.increase + end + + def pages_deployments_total_counter + @pages_deployments_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_total, "Counter of GitLab Pages deployments triggered") + end + + def pages_deployments_failed_total_counter + @pages_deployments_failed_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed") + end end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index d81035e4eba..cf69007bc3b 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -10,7 +10,7 @@ module Projects end if changing_default_branch? - project.change_head(params[:default_branch]) + return error("Could not set the default branch") unless project.change_head(params[:default_branch]) end if project.update_attributes(params.except(:default_branch)) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index c22bf7498bb..c7832c47e1a 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -511,7 +511,12 @@ module QuickActions users = extract_references(params, :user) if users.empty? - users = User.where(username: params.split(' ').map(&:strip)) + users = + if params == 'me' + [current_user] + else + User.where(username: params.split(' ').map(&:strip)) + end end users diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 17857ca62f2..14171bce782 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -1,6 +1,16 @@ class SubmitUsagePingService URL = 'https://version.gitlab.com/usage_data'.freeze + METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes + percentage_notes leader_milestones instance_milestones percentage_milestones + leader_boards instance_boards percentage_boards leader_merge_requests + instance_merge_requests percentage_merge_requests leader_ci_pipelines + instance_ci_pipelines percentage_ci_pipelines leader_environments instance_environments + percentage_environments leader_deployments instance_deployments percentage_deployments + leader_projects_prometheus_active instance_projects_prometheus_active + percentage_projects_prometheus_active leader_service_desk_issues instance_service_desk_issues + percentage_service_desk_issues].freeze + include Gitlab::CurrentSettings def execute @@ -27,15 +37,7 @@ class SubmitUsagePingService return unless response['conv_index'].present? ConversationalDevelopmentIndex::Metric.create!( - response['conv_index'].slice( - 'leader_issues', 'instance_issues', 'leader_notes', 'instance_notes', - 'leader_milestones', 'instance_milestones', 'leader_boards', 'instance_boards', - 'leader_merge_requests', 'instance_merge_requests', 'leader_ci_pipelines', - 'instance_ci_pipelines', 'leader_environments', 'instance_environments', - 'leader_deployments', 'instance_deployments', 'leader_projects_prometheus_active', - 'instance_projects_prometheus_active', 'leader_service_desk_issues', - 'instance_service_desk_issues' - ) + response['conv_index'].slice(*METRICS) ) end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index bd58a54592f..cbcd4478af6 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -24,7 +24,7 @@ class SystemHooksService key: model.key, id: model.id ) - + if model.user data[:username] = model.user.username end @@ -56,7 +56,7 @@ class SystemHooksService when GroupMember data.merge!(group_member_data(model)) end - + data end @@ -79,7 +79,7 @@ class SystemHooksService { name: model.name, path: model.path, - path_with_namespace: model.path_with_namespace, + path_with_namespace: model.full_path, project_id: model.id, owner_name: owner.name, owner_email: owner.respond_to?(:email) ? owner.email : "", @@ -93,7 +93,7 @@ class SystemHooksService { project_name: project.name, project_path: project.path, - project_path_with_namespace: project.path_with_namespace, + project_path_with_namespace: project.full_path, project_id: project.id, user_username: model.user.username, user_name: model.user.name, diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 2dbee9c246e..1763f64a4e4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -142,7 +142,8 @@ module SystemNoteService # # Returns the created Note object def change_milestone(noteable, project, author, milestone) - body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" + format = milestone&.is_group_milestone? ? :name : :iid + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}" create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone')) end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 322c6286365..6ee96d6a0f8 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -170,20 +170,22 @@ class TodoService # When user marks some todos as done def mark_todos_as_done(todos, current_user) - update_todos_state_by_ids(todos.select(&:id), current_user, :done) + update_todos_state(todos, current_user, :done) end def mark_todos_as_done_by_ids(ids, current_user) - update_todos_state_by_ids(ids, current_user, :done) + todos = todos_by_ids(ids, current_user) + mark_todos_as_done(todos, current_user) end # When user marks some todos as pending def mark_todos_as_pending(todos, current_user) - update_todos_state_by_ids(todos.select(&:id), current_user, :pending) + update_todos_state(todos, current_user, :pending) end def mark_todos_as_pending_by_ids(ids, current_user) - update_todos_state_by_ids(ids, current_user, :pending) + todos = todos_by_ids(ids, current_user) + mark_todos_as_pending(todos, current_user) end # When user marks an issue as todo @@ -198,9 +200,11 @@ class TodoService private - def update_todos_state_by_ids(ids, current_user, state) - todos = current_user.todos.where(id: ids) + def todos_by_ids(ids, current_user) + current_user.todos.where(id: Array(ids)) + end + def update_todos_state(todos, current_user, state) # Only update those that are not really on that state todos = todos.where.not(state: state) todos_ids = todos.pluck(:id) diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 27c3ba197ac..2825478926a 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -101,7 +101,7 @@ class WebHookService request_headers: build_headers(hook_name), request_data: request_data, response_headers: format_response_headers(response), - response_body: response.body, + response_body: safe_response_body(response), response_status: response.code, internal_error_message: error_message ) @@ -124,4 +124,10 @@ class WebHookService def format_response_headers(response) response.headers.each_capitalized.to_h end + + def safe_response_body(response) + return '' unless response.body + + response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '') + end end diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb index c628e6781af..93cbd9a509f 100644 --- a/app/services/wiki_pages/update_service.rb +++ b/app/services/wiki_pages/update_service.rb @@ -1,7 +1,7 @@ module WikiPages class UpdateService < WikiPages::BaseService def execute(page) - if page.update(@params[:content], format: @params[:format], message: @params[:message], last_commit_sha: @params[:last_commit_sha]) + if page.update(@params) execute_hooks(page, 'update') end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 652277e3b78..7027ac4b5db 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -30,7 +30,7 @@ class FileUploader < GitlabUploader # # Returns a String without a trailing slash def self.dynamic_path_segment(model) - File.join(CarrierWave.root, base_dir, model.path_with_namespace) + File.join(CarrierWave.root, base_dir, model.full_path) end attr_accessor :model diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 8bb2a563990..a4f49d3f6d7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -322,7 +322,7 @@ \. This setting requires a = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. - = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') + = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index') .form-group .col-sm-offset-2.col-sm-10 .checkbox diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 843c71af466..2aadc071c75 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -70,7 +70,7 @@ %span.badge = storage_counter(project.statistics.storage_size) %span.pull-right.light - %span.monospace= project.path_with_namespace + ".git" + %span.monospace= project.full_path + '.git' .panel-footer = paginate @projects, param_name: 'projects_page', theme: 'gitlab' @@ -88,7 +88,7 @@ %span.badge = storage_counter(project.statistics.storage_size) %span.pull-right.light - %span.monospace= project.path_with_namespace + ".git" + %span.monospace= project.full_path + '.git' .col-md-6 - if can?(current_user, :admin_group_member, @group) diff --git a/app/views/admin/health_check/_failing_storages.html.haml b/app/views/admin/health_check/_failing_storages.html.haml new file mode 100644 index 00000000000..6830201538d --- /dev/null +++ b/app/views/admin/health_check/_failing_storages.html.haml @@ -0,0 +1,15 @@ +- if failing_storages.any? + = _('There are problems accessing Git storage: ') + %ul + - failing_storages.each do |storage_health| + %li + = failing_storage_health_message(storage_health) + %ul + - storage_health.failing_circuit_breakers.each do |circuit_breaker| + %li + #{circuit_breaker.hostname}: #{message_for_circuit_breaker(circuit_breaker)} + + = _("Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again.") + .prepend-top-10 + = button_to _("Reset git storage health information"), reset_storage_health_admin_health_check_path, + method: :post, class: 'btn btn-default' diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index f16f59623f7..517db50b97f 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -1,22 +1,22 @@ - @no_container = true -- page_title "Health Check" +- page_title _('Health Check') +- no_errors = @errors.blank? && @failing_storage_statuses.blank? = render 'admin/monitoring/head' %div{ class: container_class } - %h3.page-title - Health Check + %h3.page-title= page_title .bs-callout.clearfix .pull-left %p - Access token is + #{ s_('HealthCheck|Access token is') } %code#health-check-token= current_application_settings.health_check_access_token .prepend-top-10 - = button_to "Reset health check access token", reset_health_check_token_admin_application_settings_path, + = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path, method: :put, class: 'btn btn-default', - data: { confirm: 'Are you sure you want to reset the health check token?' } + data: { confirm: _('Are you sure you want to reset the health check token?') } %p.light - Health information can be retrieved from the following endpoints. More information is available - = link_to 'here', help_page_path('user/admin_area/monitoring/health_check') + #{ _('Health information can be retrieved from the following endpoints. More information is available') } + = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check') %ul %li %code= readiness_url(token: current_application_settings.health_check_access_token) @@ -29,14 +29,15 @@ .panel.panel-default .panel-heading Current Status: - - if @errors.blank? + - if no_errors = icon('circle', class: 'cgreen') - Healthy + #{ s_('HealthCheck|Healthy') } - else = icon('warning', class: 'cred') - Unhealthy + #{ s_('HealthCheck|Unhealthy') } .panel-body - - if @errors.blank? - No Health Problems Detected + - if no_errors + #{ s_('HealthCheck|No Health Problems Detected') } - else = @errors + = render partial: 'failing_storages', object: @failing_storage_statuses diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index dfbc7772698..e6408f35201 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -1,6 +1,6 @@ - page_title "CI Lint" - page_description "Validate your GitLab CI configuration file" -- content_for :page_specific_javascripts do +- content_for :library_javascripts do = page_specific_javascript_tag('lib/ace.js') %h2 Check your .gitlab-ci.yml diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index ec6cb1a9624..c546252455a 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -13,10 +13,8 @@ - if show_callout?('user_callout_dismissed') = render 'shared/user_callout' - - if @projects.any? || params[:name] + - if has_projects_or_name?(@projects, params) = render 'dashboard/projects_head' - - - if @projects.any? || params[:name] = render 'projects' - else = render "zero_authorized_projects" diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index ae1d733a516..14f9f8cd70a 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -9,7 +9,7 @@ %div{ class: container_class } = render 'dashboard/projects_head' - - if @projects.any? || params[:filter_projects] + - if params[:filter_projects] || any_projects?(@projects) = render 'projects' - else %h3 You don't have starred projects yet diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 735d9390699..f83ebbf09ef 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -4,6 +4,10 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") +- content_for :page_specific_javascripts do + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'filtered_search' + - if show_new_nav? && group_issues_exists - content_for :breadcrumbs_extra do = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do @@ -20,7 +24,7 @@ Subscribe = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" - = render 'shared/issuable/filter', type: :issues + = render 'shared/issuable/search_bar', type: :issues .row-content-block.second-block Only issues from the diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 56e628a2b74..b18b3dd5766 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -121,7 +121,7 @@ .key g .key p %td - Go to the project's home page + Go to the project's overview page %tr %td.shortcut .key g diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 48edbb8c16f..f18c3a74120 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -1,5 +1,7 @@ - page_title "UI Development Kit", "Help" - lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare." +- content_for :page_specific_javascripts do + = webpack_bundle_tag('ui_development_kit') .gitlab-ui-dev-kit %h1 GitLab UI development kit @@ -407,29 +409,6 @@ .dropdown-content .dropdown-loading = icon('spinner spin') - :javascript - $('#js-project-dropdown').glDropdown({ - data: function (term, callback) { - Api.projects(term, { order_by: 'last_activity_at' }, function (data) { - callback(data); - }); - }, - text: function (project) { - return project.name_with_namespace || project.name; - }, - selectable: true, - fieldName: "author_id", - filterable: true, - search: { - fields: ['name_with_namespace'] - }, - id: function (data) { - return data.id; - }, - isSelected: function (data) { - return data.id === 2; - } - }) .example %div diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 0e7f0b5ed4f..e9a04e6c122 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -25,7 +25,7 @@ %td = provider_project_link(provider, project.import_source) %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index fde671e25a9..4dc3a4a0acf 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -4,7 +4,7 @@ job.attr("id", "project_#{@project.id}") target_field = job.find(".import-target") target_field.empty() - target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}') + target_field.append('#{link_to @project.full_path, project_path(@project)}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") - else diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index e6058617ac9..9589e0956f4 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -35,7 +35,7 @@ %td = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer' %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 5de5da5e6a2..7b832c6a23a 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -33,7 +33,7 @@ %td = project.import_source %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 7456799ca0e..37734414835 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -28,7 +28,7 @@ %td = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 767dffb5589..008e8287aa3 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -1,25 +1,43 @@ - page_title "GitLab Import" - header_title "Projects", root_path +- content_for :page_specific_javascripts do + = webpack_bundle_tag 'project_import_gl' + %h3.page-title = icon('gitlab') Import an exported GitLab project %hr -= form_tag import_gitlab_project_path, class: 'form-horizontal', multipart: true do - %p - Project will be imported as - %strong - #{@namespace.name}/#{@path} += form_tag import_gitlab_project_path, class: 'new_project', multipart: true do + .row + .form-group.col-xs-12.col-sm-6 + = label_tag :namespace_id, 'Project path', class: 'label-light' + .form-group + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1 - %p - To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. - .form-group - = hidden_field_tag :namespace_id, @namespace.id - = hidden_field_tag :path, @path - = label_tag :file, class: 'control-label' do - %span GitLab project export - .col-sm-10 - = file_field_tag :file, class: '' + - else + .input-group-addon.static-namespace + #{root_url}#{current_user.username}/ + = hidden_field_tag :namespace_id, value: current_user.namespace_id + .form-group.col-xs-12.col-sm-6.project-path + = label_tag :path, 'Project name', class: 'label-light' + = text_field_tag :path, nil, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true - .form-actions - = submit_tag 'Import project', class: 'btn btn-create' + .row + .form-group.col-md-12 + To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. + .row + .form-group.col-sm-12 + = hidden_field_tag :namespace_id, @namespace.id + = hidden_field_tag :path, @path + = label_tag :file, 'GitLab project export', class: 'label-light' + .form-group + = file_field_tag :file, class: '' + .row + .form-actions + = submit_tag 'Import project', class: 'btn btn-create' + = link_to 'Cancel', new_project_path, class: 'btn btn-cancel' diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 60de6bfe816..bc61aeece72 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -38,7 +38,7 @@ %td = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer' %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span diff --git a/app/views/layouts/_bootlint.haml b/app/views/layouts/_bootlint.haml index 69280687a9d..d603a74c4e4 100644 --- a/app/views/layouts/_bootlint.haml +++ b/app/views/layouts/_bootlint.haml @@ -1,4 +1,5 @@ +-# haml-lint:disable InlineJavaScript :javascript - jQuery(document).ready(function() { - javascript:(function(){var s=document.createElement("script");s.onload=function(){bootlint.showLintReportForCurrentDocument([], {hasProblems: false, problemFree: false});};s.src="https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js";document.body.appendChild(s)})(); - }); + window.onload = function() { + var s=document.createElement("script");s.onload=function(){bootlint.showLintReportForCurrentDocument([], {hasProblems: false, problemFree: false});};s.src="https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js";document.body.appendChild(s); + } diff --git a/app/views/layouts/_google_analytics.html.haml b/app/views/layouts/_google_analytics.html.haml index 81e03c7eff2..98ea96b0b77 100644 --- a/app/views/layouts/_google_analytics.html.haml +++ b/app/views/layouts/_google_analytics.html.haml @@ -1,3 +1,4 @@ +-# haml-lint:disable InlineJavaScript :javascript var _gaq = _gaq || []; _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']); diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 6ad22958df3..3babdae3968 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -38,6 +38,9 @@ = Gon::Base.render_data + - if content_for?(:library_javascripts) + = yield :library_javascripts + = webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "common" = webpack_bundle_tag "locale" diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 4bb0dfc73fd..fe0ec35d003 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -2,7 +2,9 @@ - noteable_type = @noteable.class if @noteable.present? - if project + -# haml-lint:disable InlineJavaScript :javascript + gl = window.gl || {}; gl.GfmAutoComplete = gl.GfmAutoComplete || {}; gl.GfmAutoComplete.dataSources = { members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}", diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml index 259b4f7cdfc..a888e8ae187 100644 --- a/app/views/layouts/_piwik.html.haml +++ b/app/views/layouts/_piwik.html.haml @@ -1,4 +1,5 @@ <!-- Piwik --> +-# haml-lint:disable InlineJavaScript :javascript var _paq = _paq || []; _paq.push(['trackPageView']); diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index bc3293fd100..b32cfe158bb 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -44,7 +44,7 @@ = icon('tachometer fw') %li = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('hashtag fw') + = custom_icon('issues') - issues_count = assigned_issuables_count(:issues) %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } = number_with_delimiter(issues_count) diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml index 60940dba475..2c1c23d6ea9 100644 --- a/app/views/layouts/header/_new.html.haml +++ b/app/views/layouts/header/_new.html.haml @@ -38,7 +38,7 @@ = icon('tachometer fw') %li = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('hashtag fw') + = custom_icon('issues') - issues_count = assigned_issuables_count(:issues) %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } = number_with_delimiter(issues_count) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 8605380848d..261445ecd2b 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -25,7 +25,7 @@ %span Members - if current_user && can?(current_user, :admin_group, @group) - = nav_link(path: %w[groups#projects groups#edit]) do + = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do = link_to edit_group_path(@group), title: 'Settings' do %span Settings diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 8db3e69aed4..0b4a9d92bea 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,16 +1,15 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to admin_root_path, title: 'Admin Overview' do .avatar-container.s40.settings-avatar = icon('wrench') .project-title Admin Area - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do - %span + .nav-icon-container + = custom_icon('overview') + %span.nav-item-name Overview %ul.sidebar-sub-level-items @@ -45,7 +44,9 @@ = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do = link_to admin_conversational_development_index_path, title: 'Monitoring' do - %span + .nav-icon-container + = custom_icon('monitoring') + %span.nav-item-name Monitoring %ul.sidebar-sub-level-items @@ -76,52 +77,74 @@ = nav_link(controller: :broadcast_messages) do = link_to admin_broadcast_messages_path, title: 'Messages' do - %span + .nav-icon-container + = custom_icon('messages') + %span.nav-item-name Messages = nav_link(controller: [:hooks, :hook_logs]) do = link_to admin_hooks_path, title: 'Hooks' do - %span + .nav-icon-container + = custom_icon('system_hooks') + %span.nav-item-name System Hooks = nav_link(controller: :applications) do = link_to admin_applications_path, title: 'Applications' do - %span + .nav-icon-container + = custom_icon('applications') + %span.nav-item-name Applications = nav_link(controller: :abuse_reports) do = link_to admin_abuse_reports_path, title: "Abuse Reports" do - %span - %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) + .nav-icon-container + = custom_icon('abuse_reports') + %span.nav-item-name Abuse Reports + %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) - if akismet_enabled? = nav_link(controller: :spam_logs) do = link_to admin_spam_logs_path, title: "Spam Logs" do - %span + .nav-icon-container + = custom_icon('spam_logs') + %span.nav-item-name Spam Logs = nav_link(controller: :deploy_keys) do = link_to admin_deploy_keys_path, title: 'Deploy Keys' do - %span + .nav-icon-container + = custom_icon('key') + %span.nav-item-name Deploy Keys = nav_link(controller: :services) do = link_to admin_application_settings_services_path, title: 'Service Templates' do - %span + .nav-icon-container + = custom_icon('service_templates') + %span.nav-item-name Service Templates = nav_link(controller: :labels) do = link_to admin_labels_path, title: 'Labels' do - %span + .nav-icon-container + = custom_icon('labels') + %span.nav-item-name Labels = nav_link(controller: :appearances) do = link_to admin_appearances_path, title: 'Appearances' do - %span + .nav-icon-container + = custom_icon('appearance') + %span.nav-item-name Appearance %li.divider = nav_link(controller: :application_settings) do = link_to admin_application_settings_path, title: 'Settings' do - %span + .nav-icon-container + = custom_icon('settings') + %span.nav-item-name Settings + + = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 4fd9e213ead..c7dabbd8237 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,18 +1,17 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to group_path(@group), title: @group.name do .avatar-container.s40.group-avatar = image_tag group_icon(@group), class: "avatar s40 avatar-tile" .group-title = @group.name - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to group_path(@group), title: 'About group' do - %span - About + = link_to group_path(@group), title: 'Group overview' do + .nav-icon-container + = custom_icon('project') + %span.nav-item-name + Overview %ul.sidebar-sub-level-items = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do @@ -27,10 +26,12 @@ = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = link_to issues_group_path(@group), title: 'Issues' do - %span + .nav-icon-container + = custom_icon('issues') + %span.nav-item-name - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.badge.count= number_with_delimiter(issues.count) Issues + %span.badge.count= number_with_delimiter(issues.count) %ul.sidebar-sub-level-items = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do @@ -50,18 +51,24 @@ = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do - %span + .nav-icon-container + = custom_icon('mr_bold') + %span.nav-item-name - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - %span.badge.count= number_with_delimiter(merge_requests.count) Merge Requests + %span.badge.count= number_with_delimiter(merge_requests.count) = nav_link(path: 'group_members#index') do = link_to group_group_members_path(@group), title: 'Members' do - %span + .nav-icon-container + = custom_icon('members') + %span.nav-item-name Members - if current_user && can?(current_user, :admin_group, @group) = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do = link_to edit_group_path(@group), title: 'Settings' do - %span + .nav-icon-container + = custom_icon('settings') + %span.nav-item-name Settings %ul.sidebar-sub-level-items = nav_link(path: 'groups#edit') do @@ -75,6 +82,8 @@ Projects = nav_link(controller: :ci_cd) do - = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do + = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do %span - Pipelines + CI / CD + + = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index 6bbd569583e..edae009a28e 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -1,61 +1,84 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to profile_path, title: 'Profile Settings' do .avatar-container.s40.settings-avatar = icon('user') .project-title User Settings - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do - %span + .nav-icon-container + = custom_icon('profile') + %span.nav-item-name Profile = nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_account_path, title: 'Account' do - %span + .nav-icon-container + = custom_icon('account') + %span.nav-item-name Account - if current_application_settings.user_oauth_applications? = nav_link(controller: 'oauth/applications') do = link_to applications_profile_path, title: 'Applications' do - %span + .nav-icon-container + = custom_icon('applications') + %span.nav-item-name Applications = nav_link(controller: :chat_names) do = link_to profile_chat_names_path, title: 'Chat' do - %span + .nav-icon-container + = custom_icon('chat') + %span.nav-item-name Chat = nav_link(controller: :personal_access_tokens) do = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do - %span + .nav-icon-container + = custom_icon('access_tokens') + %span.nav-item-name Access Tokens = nav_link(controller: :emails) do = link_to profile_emails_path, title: 'Emails' do - %span + .nav-icon-container + = custom_icon('emails') + %span.nav-item-name Emails - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do - %span + .nav-icon-container + = custom_icon('lock') + %span.nav-item-name Password = nav_link(controller: :notifications) do = link_to profile_notifications_path, title: 'Notifications' do - %span + .nav-icon-container + = custom_icon('notifications') + %span.nav-item-name Notifications = nav_link(controller: :keys) do = link_to profile_keys_path, title: 'SSH Keys' do - %span + .nav-icon-container + = custom_icon('key') + %span.nav-item-name SSH Keys = nav_link(controller: :gpg_keys) do = link_to profile_gpg_keys_path, title: 'GPG Keys' do - %span + .nav-icon-container + = custom_icon('key_2') + %span.nav-item-name GPG Keys = nav_link(controller: :preferences) do = link_to profile_preferences_path, title: 'Preferences' do - %span + .nav-icon-container + = custom_icon('preferences') + %span.nav-item-name Preferences = nav_link(path: 'profiles#audit_log') do = link_to audit_log_profile_path, title: 'Authentication log' do - %span + .nav-icon-container + = custom_icon('authentication_log') + %span.nav-item-name Authentication log + + = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index 00395b222e4..e0477c29ebe 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } - can_edit = can?(current_user, :admin_project, @project) .context-header = link_to project_path(@project), title: @project.name do @@ -6,14 +6,13 @@ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') .project-title = @project.name - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do - = link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do - %span - About + = link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do + .nav-icon-container + = custom_icon('project') + %span.nav-item-name + Overview %ul.sidebar-sub-level-items = nav_link(path: 'projects#show') do @@ -32,7 +31,9 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do - %span + .nav-icon-container + = custom_icon('doc_text') + %span.nav-item-name Repository %ul.sidebar-sub-level-items @@ -71,59 +72,58 @@ - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do - %span + .nav-icon-container + = custom_icon('container_registry') + %span.nav-item-name Registry - if project_nav_tab? :issues = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do - %span - - if @project.issues_enabled? - %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) + .nav-icon-container + = custom_icon('issues') + %span.nav-item-name Issues + - if @project.issues_enabled? + %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) %ul.sidebar-sub-level-items - - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) - = nav_link(controller: :issues) do - = link_to project_issues_path(@project), title: 'Issues' do - %span - List - - = nav_link(controller: :boards) do - = link_to project_boards_path(@project), title: 'Board' do - %span - Board + = nav_link(controller: :issues) do + = link_to project_issues_path(@project), title: 'Issues' do + %span + List - - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) - = nav_link(controller: :merge_requests) do - = link_to project_merge_requests_path(@project), title: 'Merge Requests' do - %span - Merge Requests + = nav_link(controller: :boards) do + = link_to project_boards_path(@project), title: 'Board' do + %span + Board - - if project_nav_tab? :labels - = nav_link(controller: :labels) do - = link_to project_labels_path(@project), title: 'Labels' do - %span - Labels + = nav_link(controller: :labels) do + = link_to project_labels_path(@project), title: 'Labels' do + %span + Labels - - if project_nav_tab? :milestones - = nav_link(controller: :milestones) do - = link_to project_milestones_path(@project), title: 'Milestones' do - %span - Milestones + = nav_link(controller: :milestones) do + = link_to project_milestones_path(@project), title: 'Milestones' do + %span + Milestones - if project_nav_tab? :merge_requests = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do - %span - %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) + .nav-icon-container + = custom_icon('mr_bold') + %span.nav-item-name Merge Requests + %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do - %span - Pipelines + = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do + .nav-icon-container + = custom_icon('pipeline') + %span.nav-item-name + CI / CD %ul.sidebar-sub-level-items - if project_nav_tab? :pipelines @@ -159,25 +159,31 @@ - if project_nav_tab? :wiki = nav_link(controller: :wikis) do = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do - %span + .nav-icon-container + = custom_icon('wiki') + %span.nav-item-name Wiki - if project_nav_tab? :snippets = nav_link(controller: :snippets) do = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do - %span + .nav-icon-container + = custom_icon('snippets') + %span.nav-item-name Snippets - if project_nav_tab? :settings = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do - %span + .nav-icon-container + = custom_icon('settings') + %span.nav-item-name Settings %ul.sidebar-sub-level-items - can_edit = can?(current_user, :admin_project, @project) - if can_edit - = nav_link(controller: :projects) do + = nav_link(path: %w[projects#edit]) do = link_to edit_project_path(@project), title: 'General' do %span General @@ -196,9 +202,9 @@ Repository - if @project.feature_available?(:builds, current_user) = nav_link(controller: :ci_cd) do - = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do + = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do %span - Pipelines + CI / CD - if Gitlab.config.pages.enabled = nav_link(controller: :pages) do = link_to project_pages_path(@project), title: 'Pages' do @@ -207,9 +213,13 @@ - else = nav_link(path: %w[members#show]) do - = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do - %span - Settings + = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do + .nav-icon-container + = custom_icon('members') + %span.nav-item-name + Members + + = render 'shared/sidebar_toggle_button' -# Shortcut to Project > Activity %li.hidden diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 99adb83cd1f..54d56e9b873 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -10,6 +10,7 @@ - content_for :project_javascripts do - project = @target_project || @project - if current_user + -# haml-lint:disable InlineJavaScript :javascript window.uploads_path = "#{project_uploads_path(project)}"; diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml index 57971205e0e..849075a0ba5 100644 --- a/app/views/layouts/snippets.html.haml +++ b/app/views/layouts/snippets.html.haml @@ -2,6 +2,7 @@ - content_for :page_specific_javascripts do - if @snippet && current_user + -# haml-lint:disable InlineJavaScript :javascript window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}"; diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index cf750378e25..2216708d354 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -1,5 +1,6 @@ - page_title "Personal Access Tokens" - @content_class = "limit-container-width" unless fluid_layout + = render 'profiles/head' .row.prepend-top-default @@ -19,7 +20,7 @@ %h5.prepend-top-0 Your New Personal Access Token .form-group - = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" + = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block" = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. @@ -28,8 +29,3 @@ = render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes = render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens - -:javascript - $("#created-personal-access-token").click(function() { - this.select(); - }); diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 9aed498a8a0..f08dcc0c242 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -60,9 +60,9 @@ = f.select :dashboard, dashboard_choices, {}, class: 'form-control' .form-group = f.label :project_view, class: 'label-light' do - Project home page content + Project overview content = f.select :project_view, project_view_choices, {}, class: 'form-control' .help-block - Choose what content you want to see on a project’s home page + Choose what content you want to see on a project’s overview page .form-group = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 037cb30efb9..33e062c1c9c 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -7,97 +7,92 @@ = render 'profiles/head' -- if inject_u2f_api? - - content_for :page_specific_javascripts do +- content_for :page_specific_javascripts do + - if inject_u2f_api? = page_specific_javascript_bundle_tag('u2f') + = page_specific_javascript_bundle_tag('two_factor_auth') -.row.prepend-top-default - .col-lg-4 - %h4.prepend-top-0 - Register Two-Factor Authentication App - %p - Use an app on your mobile device to enable two-factor authentication (2FA). - .col-lg-8 - - if current_user.two_factor_otp_enabled? - = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." - - else +.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path } + .row.prepend-top-default + .col-lg-4 + %h4.prepend-top-0 + Register Two-Factor Authentication App %p - Download the Google Authenticator application from App Store or Google Play Store and scan this code. - More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}. - .row.append-bottom-10 - .col-md-4 - = raw @qr_code - .col-md-8 - .account-well - %p.prepend-top-0.append-bottom-0 - Can't scan the code? - %p.prepend-top-0.append-bottom-0 - To add the entry manually, provide the following details to the application on your phone. - %p.prepend-top-0.append-bottom-0 - Account: - = @account_string - %p.prepend-top-0.append-bottom-0 - Key: - = current_user.otp_secret.scan(/.{4}/).join(' ') - %p.two-factor-new-manual-content - Time based: Yes - = form_tag profile_two_factor_auth_path, method: :post do |f| - - if @error - .alert.alert-danger - = @error - .form-group - = label_tag :pin_code, nil, class: "label-light" - = text_field_tag :pin_code, nil, class: "form-control", required: true - .prepend-top-default - = submit_tag 'Register with two-factor app', class: 'btn btn-success' + Use an app on your mobile device to enable two-factor authentication (2FA). + .col-lg-8 + - if current_user.two_factor_otp_enabled? + = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." + - else + %p + Download the Google Authenticator application from App Store or Google Play Store and scan this code. + More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}. + .row.append-bottom-10 + .col-md-4 + = raw @qr_code + .col-md-8 + .account-well + %p.prepend-top-0.append-bottom-0 + Can't scan the code? + %p.prepend-top-0.append-bottom-0 + To add the entry manually, provide the following details to the application on your phone. + %p.prepend-top-0.append-bottom-0 + Account: + = @account_string + %p.prepend-top-0.append-bottom-0 + Key: + = current_user.otp_secret.scan(/.{4}/).join(' ') + %p.two-factor-new-manual-content + Time based: Yes + = form_tag profile_two_factor_auth_path, method: :post do |f| + - if @error + .alert.alert-danger + = @error + .form-group + = label_tag :pin_code, nil, class: "label-light" + = text_field_tag :pin_code, nil, class: "form-control", required: true + .prepend-top-default + = submit_tag 'Register with two-factor app', class: 'btn btn-success' -%hr + %hr -.row.prepend-top-default - - .col-lg-4 - %h4.prepend-top-0 - Register Universal Two-Factor (U2F) Device - %p - Use a hardware device to add the second factor of authentication. - %p - As U2F devices are only supported by a few browsers, we require that you set up a - two-factor authentication app before a U2F device. That way you'll always be able to - log in - even when you're using an unsupported browser. - .col-lg-8 - - if @u2f_registration.errors.present? - = form_errors(@u2f_registration) - = render "u2f/register" + .row.prepend-top-default + .col-lg-4 + %h4.prepend-top-0 + Register Universal Two-Factor (U2F) Device + %p + Use a hardware device to add the second factor of authentication. + %p + As U2F devices are only supported by a few browsers, we require that you set up a + two-factor authentication app before a U2F device. That way you'll always be able to + log in - even when you're using an unsupported browser. + .col-lg-8 + - if @u2f_registration.errors.present? + = form_errors(@u2f_registration) + = render "u2f/register" - %hr + %hr - %h5 U2F Devices (#{@u2f_registrations.length}) + %h5 U2F Devices (#{@u2f_registrations.length}) - - if @u2f_registrations.present? - .table-responsive - %table.table.table-bordered.u2f-registrations - %colgroup - %col{ width: "50%" } - %col{ width: "30%" } - %col{ width: "20%" } - %thead - %tr - %th Name - %th Registered On - %th - %tbody - - @u2f_registrations.each do |registration| + - if @u2f_registrations.present? + .table-responsive + %table.table.table-bordered.u2f-registrations + %colgroup + %col{ width: "50%" } + %col{ width: "30%" } + %col{ width: "20%" } + %thead %tr - %td= registration.name.presence || "<no name set>" - %td= registration.created_at.to_date.to_s(:medium) - %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } - - - else - .settings-message.text-center - You don't have any U2F devices registered yet. - + %th Name + %th Registered On + %th + %tbody + - @u2f_registrations.each do |registration| + %tr + %td= registration.name.presence || "<no name set>" + %td= registration.created_at.to_date.to_s(:medium) + %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } -- if two_factor_skippable? - :javascript - var button = "<a class='btn btn-xs btn-warning pull-right' data-method='patch' href='#{skip_profile_two_factor_auth_path}'>Configure it later</a>"; - $(".flash-alert").append(button); + - else + .settings-message.text-center + You don't have any U2F devices registered yet. diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index ecc966ed453..ad63f5e73ae 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -8,9 +8,3 @@ .content_list.project-activity{ :"data-href" => activity_project_path(@project) } = spinner - -:javascript - var activity = new gl.Activities(); - $(document).on('page:restore', function (event) { - activity.reloadActivities() - }) diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 426085b3e1c..3a7a99462a6 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -1,14 +1,13 @@ - commit = local_assigns.fetch(:commit) { @repository.commit } - ref = local_assigns.fetch(:ref) { current_ref } - project = local_assigns.fetch(:project) { @project } +- content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) } + #tree-holder.tree-holder.clearfix .nav-block = render 'projects/tree/tree_header', tree: @tree - - if commit - .info-well.hidden-xs.project-last-commit.append-bottom-default - .well-segment - %ul.blob-commit-info - = render 'projects/commits/commit', commit: commit, ref: ref, project: project + - if !show_new_repo? && commit + = render 'shared/commit_well', commit: commit, ref: ref, project: project - = render 'projects/tree/tree_content', tree: @tree + = render 'projects/tree/tree_content', tree: @tree, content_url: content_url diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index d0698285f84..6e13bf47ff6 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,5 +1,12 @@ - referenced_users = local_assigns.fetch(:referenced_users, nil) +- if defined?(@issue) && @issue.confidential? + %li.confidential-issue-warning + = confidential_icon(@issue) + %span This is a confidential issue. Your comment will not be visible to the public. +- else + %li.confidential-issue-warning.not-confidential + .md-area .md-header %ul.nav-links.clearfix @@ -10,11 +17,6 @@ %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } Preview - - if defined?(@issue) && @issue.confidential? - %li.confidential-issue-warning - = icon('warning') - %span This is a confidential issue. Your comment will not be visible to the public. - %li.pull-right .toolbar-group = markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 818010bc7d3..cc5afa943cf 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -1,8 +1,3 @@ - form = local_assigns.fetch(:form) -%fieldset.features.merge-requests-feature.append-bottom-default - %hr - %h5.prepend-top-0 - Merge Requests - - = render 'projects/merge_request_merge_settings', form: form += render 'projects/merge_request_merge_settings', form: form diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml new file mode 100644 index 00000000000..21baf35f2ac --- /dev/null +++ b/app/views/projects/_project_templates.html.haml @@ -0,0 +1,10 @@ +.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } } + .btn.blank-option.active + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" } + = icon('file-o', class: 'btn-template-icon') + Blank + - Gitlab::ProjectTemplate.all.each do |template| + .btn + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name } + = custom_icon(template.logo) + = template.title diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml index 18e86ac5a92..b85bbcb980e 100644 --- a/app/views/projects/artifacts/file.html.haml +++ b/app/views/projects/artifacts/file.html.haml @@ -3,7 +3,7 @@ = render "projects/jobs/header", show_controls: false -#tree-holder.tree-holder +.tree-holder .nav-block %ul.breadcrumb.repo-breadcrumb %li diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 8bd336269ff..849716a679b 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -9,5 +9,5 @@ #blob-content-holder.blob-content-holder %article.file-holder - = render "projects/blob/header", blob: blob + = render 'projects/blob/header', blob: blob = render 'projects/blob/content', blob: blob diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index b2959ef6d31..03ab1bb59e4 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -20,6 +20,3 @@ - unless can?(current_user, :push_code, @project) .inline.prepend-left-10 = commit_in_fork_help - -:javascript - new NewCommitForm($('.js-create-dir-form')) diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 6a4a657fa8c..750bdef3308 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -13,6 +13,3 @@ .col-sm-offset-2.col-sm-10 = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" - -:javascript - new NewCommitForm($('.js-delete-blob-form')) diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 32dbc1b3417..05b7dfe2872 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -19,7 +19,9 @@ = render 'shared/new_commit_form', placeholder: placeholder .form-actions - = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' + = button_tag class: 'btn btn-create btn-upload-file', id: 'submit-all', type: 'button' do + = icon('spin spinner', class: 'js-loading-icon hidden' ) + = button_title = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal" - unless can?(current_user, :push_code, @project) diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml index 013f1c267c8..cc85e5de40f 100644 --- a/app/views/projects/blob/_viewer.html.haml +++ b/app/views/projects/blob/_viewer.html.haml @@ -17,3 +17,4 @@ - viewer = BlobViewer::Download.new(viewer.blob) if viewer.binary_detected_after_load? = render viewer.partial_path, viewer: viewer + diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 7dd834e84b5..240e62d5ac5 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -5,16 +5,23 @@ = render "projects/commits/head" - content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('blob') + = webpack_bundle_tag 'blob' + + - if show_new_repo? + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'repo' = render 'projects/last_push' %div{ class: container_class } - #tree-holder.tree-holder - = render 'blob', blob: @blob + - if show_new_repo? + = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_blob_path(@project, @id) + - else + #tree-holder.tree-holder + = render 'blob', blob: @blob - - if can_modify_blob?(@blob) - = render 'projects/blob/remove' + - if can_modify_blob?(@blob) + = render 'projects/blob/remove' - - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put + - title = "Replace #{@blob.name}" + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put diff --git a/app/views/projects/blob/viewers/_balsamiq.html.haml b/app/views/projects/blob/viewers/_balsamiq.html.haml index 28670e7de97..1e7c461f02e 100644 --- a/app/views/projects/blob/viewers/_balsamiq.html.haml +++ b/app/views/projects/blob/viewers/_balsamiq.html.haml @@ -1,4 +1,4 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('balsamiq_viewer') -.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_url } } +.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } } diff --git a/app/views/projects/blob/viewers/_download.html.haml b/app/views/projects/blob/viewers/_download.html.haml index 684240d02c7..6d1138f7959 100644 --- a/app/views/projects/blob/viewers/_download.html.haml +++ b/app/views/projects/blob/viewers/_download.html.haml @@ -1,6 +1,6 @@ .file-content.blob_file.blob-no-preview .center - = link_to blob_raw_url do + = link_to blob_raw_path do %h1.light = icon('download') %h4 diff --git a/app/views/projects/blob/viewers/_image.html.haml b/app/views/projects/blob/viewers/_image.html.haml index 5fd22a59217..26ea028c5d7 100644 --- a/app/views/projects/blob/viewers/_image.html.haml +++ b/app/views/projects/blob/viewers/_image.html.haml @@ -1,2 +1,2 @@ .file-content.image_file - = image_tag(blob_raw_url, alt: viewer.blob.name) + = image_tag(blob_raw_path, alt: viewer.blob.name) diff --git a/app/views/projects/blob/viewers/_notebook.html.haml b/app/views/projects/blob/viewers/_notebook.html.haml index 2399fb16265..8a41bc53004 100644 --- a/app/views/projects/blob/viewers/_notebook.html.haml +++ b/app/views/projects/blob/viewers/_notebook.html.haml @@ -2,4 +2,4 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('notebook_viewer') -.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_url } } +.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } } diff --git a/app/views/projects/blob/viewers/_pdf.html.haml b/app/views/projects/blob/viewers/_pdf.html.haml index 1dd179c4fdc..ec2b18bd4ab 100644 --- a/app/views/projects/blob/viewers/_pdf.html.haml +++ b/app/views/projects/blob/viewers/_pdf.html.haml @@ -2,4 +2,4 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('pdf_viewer') -.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_url } } +.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } } diff --git a/app/views/projects/blob/viewers/_sketch.html.haml b/app/views/projects/blob/viewers/_sketch.html.haml index 49f716c2c59..775e4584f77 100644 --- a/app/views/projects/blob/viewers/_sketch.html.haml +++ b/app/views/projects/blob/viewers/_sketch.html.haml @@ -2,6 +2,6 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('sketch_viewer') -.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_url } } +.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } } .js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' } = icon('spinner spin 2x', 'aria-hidden' => 'true'); diff --git a/app/views/projects/blob/viewers/_stl.html.haml b/app/views/projects/blob/viewers/_stl.html.haml index e4e9d746176..6578d826ace 100644 --- a/app/views/projects/blob/viewers/_stl.html.haml +++ b/app/views/projects/blob/viewers/_stl.html.haml @@ -2,7 +2,7 @@ = page_specific_javascript_bundle_tag('stl_viewer') .file-content.is-stl-loading - .text-center#js-stl-viewer{ data: { endpoint: blob_raw_url } } + .text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } } = icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading') .text-center.prepend-top-default.append-bottom-default.stl-controls .btn-group diff --git a/app/views/projects/blob/viewers/_video.html.haml b/app/views/projects/blob/viewers/_video.html.haml index 595a890a27d..36039c08d52 100644 --- a/app/views/projects/blob/viewers/_video.html.haml +++ b/app/views/projects/blob/viewers/_video.html.haml @@ -1,2 +1,2 @@ .file-content.video - %video{ src: blob_raw_url, controls: true, data: { setup: '{}' } } + %video{ src: blob_raw_path, controls: true, data: { setup: '{}' } } diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 539ee087b14..64f5f6d7ba0 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -6,8 +6,16 @@ %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable", ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }", "aria-hidden": "true" } - %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', - data: { container: "body", placement: "bottom" } } + + %span.has-tooltip{ "v-if": "list.type !== \"label\"", + ":title" => '(list.label ? list.label.description : "")' } + {{ list.title }} + + %span.has-tooltip{ "v-if": "list.type === \"label\"", + ":title" => '(list.label ? list.label.description : "")', + data: { container: "body", placement: "bottom" }, + class: "label color-label title", + ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" } {{ list.title }} .issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 03eefcc2b4d..2baaaf6ac5b 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -28,8 +28,4 @@ .form-actions = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' - -:javascript - var availableRefs = #{@project.repository.ref_names.to_json}; - - new NewBranchForm($('.js-create-branch-form'), availableRefs) +%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 419fbe99af8..09bcd187e59 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,4 +1,4 @@ -.page-content-header +.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) } .header-main-content = render partial: 'signature', object: @commit.signature %strong @@ -79,6 +79,3 @@ = render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph' in = time_interval_in_words last_pipeline.duration - -:javascript - $(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}"); diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 12b73ecdf13..e7da47032be 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -5,7 +5,7 @@ - notes = commit.notes - note_count = notes.user.count -- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)] +- cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)] - cache_key.push(commit.status(ref)) if commit.status(ref) = cache(cache_key, expires_in: 1.day) do diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index bd2d900997e..7ae56086177 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -11,34 +11,32 @@ = content_for :sub_nav do = render "head" -%div{ class: container_class } - .tree-holder - .nav-block - .tree-ref-container - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' +.js-project-commits-show{ 'data-commits-limit' => @limit } + %div{ class: container_class } + .tree-holder + .nav-block + .tree-ref-container + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' + + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .tree-controls.hidden-xs.hidden-sm + - if @merge_request.present? + .control + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' + - elsif create_mr_button?(@repository.root_ref, @ref) + .control + = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - .tree-controls.hidden-xs.hidden-sm - - if @merge_request.present? .control - = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' - - elsif create_mr_button?(@repository.root_ref, @ref) + = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do + = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } .control - = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do + = icon("rss") - .control - = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do - = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } - .control - = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do - = icon("rss") - - %div{ id: dom_id(@project) } - %ol#commits-list.list-unstyled.content_list - = render 'commits', project: @project, ref: @ref - = spinner - -:javascript - CommitsList.init(#{@limit}); + %div{ id: dom_id(@project) } + %ol#commits-list.list-unstyled.content_list + = render 'commits', project: @project, ref: @ref + = spinner diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index f9385459a66..25a5dfc2aaa 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,21 +3,22 @@ - can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project) - diff_files = diffs.diff_files -.content-block.oneline-block.files-changed - .inline-parallel-buttons - - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? } - = link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default' - - if show_whitespace_toggle - - if current_controller?(:commit) - = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') - - elsif current_controller?('projects/merge_requests/diffs') - = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs') - - elsif current_controller?(:compare) - = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs') - .btn-group - = inline_diff_btn - = parallel_diff_btn - = render 'projects/diffs/stats', diff_files: diff_files +.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed + .files-changed-inner + .inline-parallel-buttons + - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? } + = link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default' + - if show_whitespace_toggle + - if current_controller?(:commit) + = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') + - elsif current_controller?('projects/merge_requests/diffs') + = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs') + - elsif current_controller?(:compare) + = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs') + .btn-group + = inline_diff_btn + = parallel_diff_btn + = render 'projects/diffs/stats', diff_files: diff_files - if render_overflow_warning?(diff_files) = render 'projects/diffs/warning', diff_files: diffs diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index e69c7f20d49..efc0ea31917 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -1,36 +1,34 @@ -.js-toggle-container - .commit-stat-summary - Showing - %button.diff-stats-summary-toggler.js-toggle-button{ type: "button" } - %strong= pluralize(diff_files.size, "changed file") +- sum_added_lines = diff_files.sum(&:added_lines) +- sum_removed_lines = diff_files.sum(&:removed_lines) +.commit-stat-summary.dropdown + Showing + %button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown" } }< + = pluralize(diff_files.size, "changed file") + = icon("caret-down", class: "prepend-left-5") + %span.diff-stats-additions-deletions-expanded#diff-stats with - %strong.cgreen #{diff_files.sum(&:added_lines)} additions + %strong.cgreen #{sum_added_lines} additions and - %strong.cred #{diff_files.sum(&:removed_lines)} deletions - .file-stats.js-toggle-content.hide - %ul - - diff_files.each do |diff_file| - - file_hash = hexdigest(diff_file.file_path) - %li - - if diff_file.deleted_file? - %span.deleted-file - %a{ href: "##{file_hash}" } - %i.fa.fa-minus - = diff_file.old_path - - elsif diff_file.renamed_file? - %span.renamed-file - %a{ href: "##{file_hash}" } - %i.fa.fa-minus - = diff_file.old_path - → - = diff_file.new_path - - elsif diff_file.new_file? - %span.new-file - %a{ href: "##{file_hash}" } - %i.fa.fa-plus - = diff_file.new_path - - else - %span.edit-file - %a{ href: "##{file_hash}" } - %i.fa.fa-adjust - = diff_file.new_path + %strong.cred #{sum_removed_lines} deletions + .diff-stats-additions-deletions-collapsed.pull-right{ "aria-hidden": "true", "aria-describedby": "diff-stats" } + %strong.cgreen< + +#{sum_added_lines} + %strong.cred< + \-#{sum_removed_lines} + .dropdown-menu.diff-file-changes + = dropdown_filter("Search files") + .dropdown-content + %ul + - diff_files.each do |diff_file| + %li + %a{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } + = icon("#{diff_file_changed_icon(diff_file)} fw", class: "#{diff_file_changed_icon_color(diff_file)} append-right-5") + %span.diff-file-changes-path= diff_file.new_path + .pull-right + %span.cgreen< + +#{diff_file.added_lines} + %span.cred< + \-#{diff_file.removed_lines} + %li.dropdown-menu-empty-link.hidden + %a{ href: "#" } + No files found. diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 087cb804449..20fceda26dc 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,12 +1,19 @@ +- page_title "General" - @content_class = "limit-container-width" unless fluid_layout +- expanded = Rails.env.test? = render "projects/settings/head" + .project-edit-container - .row.prepend-top-default - .col-lg-4.profile-settings-sidebar - %h4.prepend-top-0 - Project settings - .col-lg-8 + %section.settings.general-settings + .settings-header + %h4 + General project settings + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Update your project name, description, avatar, and other general settings. + .settings-content.no-animate{ class: ('expanded' if expanded) } .project-edit-errors = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| %fieldset @@ -35,89 +42,7 @@ = f.label :tag_list, "Tags", class: 'label-light' = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" %p.help-block Separate tags with commas. - %hr - %fieldset - %h5.prepend-top-0 - Sharing & Permissions - .form_group.prepend-top-20.sharing-and-permissions - .row.js-visibility-select - .col-md-8 - .label-light - = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level - = link_to icon('question-circle'), help_page_path("public_access/public_access") - %span.help-block - .col-md-4.visibility-select-container - = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level) - = f.fields_for :project_feature do |feature_fields| - %fieldset.features - .row - .col-md-8.project-feature - = feature_fields.label :repository_access_level, "Repository", class: 'label-light' - %span.help-block View and edit files in this project - .col-md-4.js-repo-access-level - = project_feature_access_select(:repository_access_level) - - .row - .col-md-8.project-feature.nested - = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' - %span.help-block Submit changes to be merged upstream - .col-md-4 - = project_feature_access_select(:merge_requests_access_level) - - .row - .col-md-8.project-feature.nested - = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light' - %span.help-block Build, test, and deploy your changes - .col-md-4 - = project_feature_access_select(:builds_access_level) - - .row - .col-md-8.project-feature - = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' - %span.help-block Share code pastes with others out of Git repository - .col-md-4 - = project_feature_access_select(:snippets_access_level) - - .row - .col-md-8.project-feature - = feature_fields.label :issues_access_level, "Issues", class: 'label-light' - %span.help-block Lightweight issue tracking system for this project - .col-md-4 - = project_feature_access_select(:issues_access_level) - - .row - .col-md-8.project-feature - = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' - %span.help-block Pages for project documentation - .col-md-4 - = project_feature_access_select(:wiki_access_level) - .form-group - = render 'shared/allow_request_access', form: f - - if Gitlab.config.lfs.enabled && current_user.admin? - .row.js-lfs-enabled - .col-md-8 - = f.label :lfs_enabled, 'LFS', class: 'label-light' - %span.help-block - Git Large File Storage - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - .col-md-4 - .select-wrapper - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' } - = icon('chevron-down') - - if Gitlab.config.registry.enabled - .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) } - .checkbox - = f.label :container_registry_enabled do - = f.check_box :container_registry_enabled - %strong Container Registry - %br - %span.descr Enable Container Registry for this project - = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' - - = render 'merge_request_settings', form: f - - %hr - %fieldset.features.append-bottom-default + %fieldset.features %h5.prepend-top-0 Project avatar .form-group @@ -137,41 +62,114 @@ = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" - .row.prepend-top-default - %hr - .row.prepend-top-default - .col-lg-4 - %h4.prepend-top-0 - Housekeeping - %p.append-bottom-0 - %p - Runs a number of housekeeping tasks within the current repository, - such as compressing file revisions and removing unreachable objects. - .col-lg-8 - = link_to 'Housekeeping', housekeeping_project_path(@project), - method: :post, class: "btn btn-default" - %hr - .row.prepend-top-default - .col-lg-4 - %h4.prepend-top-0 - Export project - %p.append-bottom-0 - %p - Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page. - %p - Once the exported file is ready, you will receive a notification email with a download link. + %section.settings.sharing-permissions + .settings-header + %h4 + Sharing and permissions + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Enable or disable certain project features and choose access levels. + .settings-content.no-animate{ class: ('expanded' if expanded) } + = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f| + .form_group.sharing-and-permissions + .row.js-visibility-select + .col-md-8 + .label-light + = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level + = link_to icon('question-circle'), help_page_path("public_access/public_access") + %span.help-block + .col-md-4.visibility-select-container + = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level) + = f.fields_for :project_feature do |feature_fields| + %fieldset.features + .row + .col-md-8.project-feature + = feature_fields.label :repository_access_level, "Repository", class: 'label-light' + %span.help-block View and edit files in this project + .col-md-4.js-repo-access-level + = project_feature_access_select(:repository_access_level) - .col-lg-8 + .row + .col-md-8.project-feature.nested + = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' + %span.help-block Submit changes to be merged upstream + .col-md-4 + = project_feature_access_select(:merge_requests_access_level) - - if @project.export_project_path - = link_to 'Download export', download_export_project_path(@project), - rel: 'nofollow', download: '', method: :get, class: "btn btn-default" - = link_to 'Generate new export', generate_new_export_project_path(@project), - method: :post, class: "btn btn-default" - - else - = link_to 'Export project', export_project_path(@project), - method: :post, class: "btn btn-default" + .row + .col-md-8.project-feature.nested + = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light' + %span.help-block Build, test, and deploy your changes + .col-md-4 + = project_feature_access_select(:builds_access_level) + + .row + .col-md-8.project-feature + = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' + %span.help-block Share code pastes with others out of Git repository + .col-md-4 + = project_feature_access_select(:snippets_access_level) + + .row + .col-md-8.project-feature + = feature_fields.label :issues_access_level, "Issues", class: 'label-light' + %span.help-block Lightweight issue tracking system for this project + .col-md-4 + = project_feature_access_select(:issues_access_level) + + .row + .col-md-8.project-feature + = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' + %span.help-block Pages for project documentation + .col-md-4 + = project_feature_access_select(:wiki_access_level) + .form-group + = render 'shared/allow_request_access', form: f + - if Gitlab.config.lfs.enabled && current_user.admin? + .row.js-lfs-enabled.form-group.sharing-and-permissions + .col-md-8 + = f.label :lfs_enabled, 'Git Large File Storage', class: 'label-light' + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + %span.help-block Manages large files such as audio, video and graphics files. + .col-md-4 + .select-wrapper + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' } + = icon('chevron-down') + - if Gitlab.config.registry.enabled + .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) } + .checkbox + = f.label :container_registry_enabled do + = f.check_box :container_registry_enabled + %strong Container Registry + %br + %span.descr Enable Container Registry for this project + = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' + = f.submit 'Save changes', class: "btn btn-save" + + + %section.settings.merge-requests-feature{ style: ("display: none;" if @project.project_feature.send(:merge_requests_access_level) == 0) } + .settings-header + %h4 + Merge request settings + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Customize your merge request restrictions. + .settings-content.no-animate{ class: ('expanded' if expanded) } + = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f| + = render 'merge_request_settings', form: f + = f.submit 'Save changes', class: "btn btn-save" + %section.settings + .settings-header + %h4 + Export project + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page. + .settings-content.no-animate{ class: ('expanded' if expanded) } .bs-callout.bs-callout-info %p.append-bottom-0 %p @@ -189,110 +187,117 @@ %li Container registry images %li CI variables %li Any encrypted tokens - - if can? current_user, :archive_project, @project - %hr - .row.prepend-top-default - .col-lg-4 - %h4.warning-title.prepend-top-0 - - if @project.archived? - Unarchive project - - else - Archive project - %p.append-bottom-0 + %p + Once the exported file is ready, you will receive a notification email with a download link. + - if @project.export_project_path + = link_to 'Download export', download_export_project_path(@project), + rel: 'nofollow', download: '', method: :get, class: "btn btn-default" + = link_to 'Generate new export', generate_new_export_project_path(@project), + method: :post, class: "btn btn-default" + - else + = link_to 'Export project', export_project_path(@project), + method: :post, class: "btn btn-default" + + %section.settings.advanced-settings + .settings-header + %h4 + Advanced settings + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Perform advanced options such as housekeeping, exporting, archiveing, renameing, transfering, or removeing your project. + .settings-content.no-animate{ class: ('expanded' if expanded) } + .sub-section + %h4 Housekeeping + %p + Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects. + = link_to 'Run housekeeping', housekeeping_project_path(@project), + method: :post, class: "btn btn-default" + - if can? current_user, :archive_project, @project + .sub-section + %h4.warning-title + - if @project.archived? + Unarchive project + - else + Archive project - if @project.archived? - Unarchiving the project will mark its repository as active. The project can be committed to. + %p + Unarchiving the project will mark its repository as active. The project can be committed to. + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive project', unarchive_project_path(@project), + data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, + method: :post, class: "btn btn-success" - else - Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches. - .col-lg-8 - - if @project.archived? - %p - %strong Once active this project shows up in the search and on the dashboard. - = link_to 'Unarchive project', unarchive_project_path(@project), - data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, - method: :post, class: "btn btn-success" - - else - %p - %strong Archived projects cannot be committed to! - = link_to 'Archive project', archive_project_path(@project), - data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, - method: :post, class: "btn btn-warning" - %hr - .row.prepend-top-default - .col-lg-4 - %h4.prepend-top-0.warning-title - Rename repository - .col-lg-8 - = render 'projects/errors' - = form_for([@project.namespace.becomes(Namespace), @project]) do |f| - .form-group.project_name_holder - = f.label :name, class: 'label-light' do - Project name - .form-group - = f.text_field :name, class: "form-control" - .form-group - = f.label :path, class: 'label-light' do - %span Path - .form-group - .input-group - .input-group-addon - #{URI.join(root_url, @project.namespace.full_path)}/ - = f.text_field :path, class: 'form-control' - %ul - %li Be careful. Renaming a project's repository can have unintended side effects. - %li You will need to update your local repositories to point to the new location. - - if @project.deployment_services.any? - %li Your deployment services will be broken, you will need to manually fix the services after renaming. - = f.submit 'Rename project', class: "btn btn-warning" - - if can?(current_user, :change_namespace, @project) - %hr - .row.prepend-top-default - .col-lg-4 - %h4.prepend-top-0.danger-title - Transfer project to new group - %p.append-bottom-0 - Please select the group you want to transfer this project to in the dropdown to the right. - .col-lg-8 - = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f| + %p + Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches. + %strong Archived projects cannot be committed to! + = link_to 'Archive project', archive_project_path(@project), + data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, + method: :post, class: "btn btn-warning" + .sub-section.rename-respository + %h4.warning-title + Rename repository + %p + Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page. + = render 'projects/errors' + = form_for([@project.namespace.becomes(Namespace), @project]) do |f| + .form-group.project_name_holder + = f.label :name, class: 'label-light' do + Project name + .form-group + = f.text_field :name, class: "form-control" .form-group - = label_tag :new_namespace_id, nil, class: 'label-light' do - %span Select a new namespace + = f.label :path, class: 'label-light' do + %span Path .form-group - = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2' + .input-group + .input-group-addon + #{URI.join(root_url, @project.namespace.full_path)}/ + = f.text_field :path, class: 'form-control' %ul - %li Be careful. Changing the project's namespace can have unintended side effects. - %li You can only transfer the project to namespaces you manage. + %li Be careful. Renaming a project's repository can have unintended side effects. %li You will need to update your local repositories to point to the new location. - %li Project visibility level will be changed to match namespace rules when transfering to a group. - = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } - - if @project.forked? && can?(current_user, :remove_fork_project, @project) - %hr - .row.prepend-top-default.append-bottom-default - .col-lg-4 - %h4.prepend-top-0.danger-title - Remove fork relationship - %p.append-bottom-0 + - if @project.deployment_services.any? + %li Your deployment services will be broken, you will need to manually fix the services after renaming. + = f.submit 'Rename project', class: "btn btn-warning" + - if can?(current_user, :change_namespace, @project) + .sub-section + %h4.danger-title + Transfer project + = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f| + .form-group + = label_tag :new_namespace_id, nil, class: 'label-light' do + %span Select a new namespace + .form-group + = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2' + %ul + %li Be careful. Changing the project's namespace can have unintended side effects. + %li You can only transfer the project to namespaces you manage. + %li You will need to update your local repositories to point to the new location. + %li Project visibility level will be changed to match namespace rules when transfering to a group. + = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } + - if @project.forked? && can?(current_user, :remove_fork_project, @project) + .sub-section + %h4.danger-title + Remove fork relationship %p This will remove the fork relationship to source project = succeed "." do = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) - .col-lg-8 - = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| - %p - %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. - = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) } - - if can?(current_user, :remove_project, @project) - %hr - .row.prepend-top-default.append-bottom-default - .col-lg-4 - %h4.prepend-top-0.danger-title - Remove project - %p.append-bottom-0 - Removing the project will delete its repository and all related resources including issues, merge requests etc. - .col-lg-8 - = form_tag(project_path(@project), method: :delete) do + = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| + %p + %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. + = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) } + - if can?(current_user, :remove_project, @project) + .sub-section + %h4.danger-title + Remove project %p - %strong Removed projects cannot be restored! - = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } + Removing the project will delete its repository and all related resources including issues, merge requests etc. + = form_tag(project_path(@project), method: :delete) do + %p + %strong Removed projects cannot be restored! + = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } .save-project-loader.hide .center diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index e3bf48ee47f..021575160ea 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -1,7 +1,7 @@ - page_title "Find File", @ref = render "projects/commits/head" -.file-finder-holder.tree-holder.clearfix +.file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) } .nav-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'find_file', path: @path @@ -17,11 +17,3 @@ %table.table.files-slider{ class: "table_#{@hex_path} tree-table table-striped" } %tbody = spinner nil, true - -:javascript - var projectFindFile = new ProjectFindFile($(".file-finder-holder"), { - url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", - treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}", - blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}" - }); - new ShortcutsFindFile(projectFindFile); diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 249b9d82ad9..9f5a1239a82 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -3,8 +3,9 @@ - if show_new_nav? - add_to_breadcrumbs("Repository", project_tree_path(@project)) - content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('common_d3') - = page_specific_javascript_bundle_tag('graphs') + = webpack_bundle_tag('common_d3') + = webpack_bundle_tag('graphs') + = webpack_bundle_tag('graphs_charts') = render "projects/commits/head" .repo-charts{ class: container_class } @@ -75,55 +76,10 @@ Commits per day hour (UTC) %canvas#hour-chart -:javascript - var responsiveChart = function (selector, data) { - var options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false }; - // get selector by context - var ctx = selector.get(0).getContext("2d"); - // pointing parent container to make chart.js inherit its width - var container = $(selector).parent(); - var generateChart = function() { - selector.attr('width', $(container).width()); - if (window.innerWidth < 768) { - // Scale fonts if window width lower than 768px (iPad portrait) - options.scaleFontSize = 8 - } - return new Chart(ctx).Bar(data, options); - }; - // enabling auto-resizing - $(window).resize(generateChart); - return generateChart(); - }; - - var chartData = function (keys, values) { - var data = { - labels : keys, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : values - }] - }; - return data; - }; - - var hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json}); - responsiveChart($('#hour-chart'), hourData); - - var dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json}); - responsiveChart($('#weekday-chart'), dayData); - - var monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}); - responsiveChart($('#month-chart'), monthData); - - var data = #{@languages.to_json}; - var ctx = $("#languages-chart").get(0).getContext("2d"); - var options = { - scaleOverlay: true, - responsive: true, - maintainAspectRatio: false - } - var myPieChart = new Chart(ctx).Pie(data, options); +%script#projectChartData{ type: "application/json" } + - projectChartData = {}; + - projectChartData['hour'] = @commits_per_time + - projectChartData['weekDays'] = @commits_per_week_days + - projectChartData['month'] = @commits_per_month + - projectChartData['languages'] = @languages + = projectChartData.to_json.html_safe diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 4256a8c4d7e..f41a0d8293b 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,15 +1,16 @@ - @no_container = true - page_title "Contributors" - content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('common_d3') - = page_specific_javascript_bundle_tag('graphs') + = webpack_bundle_tag('common_d3') + = webpack_bundle_tag('graphs') + = webpack_bundle_tag('graphs_show') - if show_new_nav? - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render 'projects/commits/head' -%div{ class: container_class } +.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } .sub-header-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' @@ -33,24 +34,3 @@ #contributors-master #contributors.clearfix %ol.contributors-list.clearfix - - - -:javascript - $.ajax({ - type: "GET", - url: "#{project_graph_path(@project, current_ref, format: :json)}", - dataType: "json", - success: function (data) { - var graph = new ContributorsStatGraph(); - graph.init(data); - - $("#brush_change").change(function(){ - graph.change_date_header(); - graph.redraw_authors(); - }); - - $(".stat-graph").fadeIn(); - $(".loading-graph").hide(); - } - }); diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index c52b3860636..8c490773a56 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -10,5 +10,3 @@ - if @project.external_import? %p.monospace git clone --bare #{@project.safe_import_url} %p Please wait while we import the repository for you. Refresh at will. - :javascript - new ProjectImport(); diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index a57844f974e..ad5befc6ee5 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -19,7 +19,8 @@ = icon('angle-double-left') .issuable-meta - = confidential_icon(@issue) + - if @issue.confidential + = icon('eye-slash', class: 'is-confidential') = issuable_meta(@issue, @project, "Issue") .issuable-actions diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index f2db71e8838..99f4b30d085 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -1,101 +1,101 @@ - builds = @build.pipeline.builds.to_a %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } - .blocks-container - .block - %strong - = @build.name - %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' } - = icon('angle-double-right') - - #js-details-block-vue - - - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + .sidebar-container + .blocks-container .block - .title - Job artifacts - - if @build.artifacts_expired? - %p.build-detail-row - The artifacts were removed - #{time_ago_with_tooltip(@build.artifacts_expire_at)} - - elsif @build.has_expiring_artifacts? - %p.build-detail-row - The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at + %strong + = @build.name + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' } + = icon('angle-double-right') - - if @build.artifacts? - .btn-group.btn-group-justified{ role: :group } - - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) - = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do - Keep + #js-details-block-vue - = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do - Download + - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + .block + .title + Job artifacts + - if @build.artifacts_expired? + %p.build-detail-row + The artifacts were removed + #{time_ago_with_tooltip(@build.artifacts_expire_at)} + - elsif @build.has_expiring_artifacts? + %p.build-detail-row + The artifacts will be removed in + %span.js-artifacts-remove= @build.artifacts_expire_at - - if @build.artifacts_metadata? - = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do - Browse + - if @build.artifacts? + .btn-group.btn-group-justified{ role: :group } + - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) + = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do + Keep - - if @build.trigger_request - .build-widget.block - %h4.title - Trigger + = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do + Download - %p - %span.build-light-text Token: - #{@build.trigger_request.trigger.short_token} + - if @build.artifacts_metadata? + = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do + Browse + + - if @build.trigger_request + .build-widget.block + %h4.title + Trigger - - if @build.trigger_request.variables %p - %button.btn.group.btn-group-justified.reveal-variables Reveal Variables + %span.build-light-text Token: + #{@build.trigger_request.trigger.short_token} + - if @build.trigger_request.variables + %p + %button.btn.group.btn-group-justified.reveal-variables Reveal Variables - - @build.trigger_request.variables.each do |key, value| - .hide.js-build - .js-build-variable.trigger-build-variable= key - .js-build-value.trigger-build-value= value + %dl.js-build-variables.trigger-build-variables.hide + - @build.trigger_request.variables.each do |key, value| + %dt.js-build-variable.trigger-build-variable= key + %dd.js-build-value.trigger-build-value= value - %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } - %p - Commit - = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' - = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard") - - if @build.merge_request - in - = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit' + %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } + %p + Commit + = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' + = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard") + - if @build.merge_request + in + = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit' - %p.build-light-text.append-bottom-0 - #{@build.pipeline.git_commit_title} + %p.build-light-text.append-bottom-0 + #{@build.pipeline.git_commit_title} - - if @build.pipeline.stages_count > 1 - .dropdown.build-dropdown - %div - %span{ class: "ci-status-icon-#{@build.pipeline.status}" } - = ci_icon_for_status(@build.pipeline.status) - Pipeline - = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' - from - = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit' - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.stage-selection More - = icon('chevron-down') - %ul.dropdown-menu - - @build.pipeline.legacy_stages.each do |stage| - %li - %a.stage-item= stage.name + - if @build.pipeline.stages_count > 1 + .block-last.dropdown.build-dropdown + %div + %span{ class: "ci-status-icon-#{@build.pipeline.status}" } + = ci_icon_for_status(@build.pipeline.status) + Pipeline + = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' + from + = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit' + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.stage-selection More + = icon('chevron-down') + %ul.dropdown-menu + - @build.pipeline.legacy_stages.each do |stage| + %li + %a.stage-item= stage.name - .builds-container - - HasStatus::ORDERED_STATUSES.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - = link_to project_job_path(@project, build) do - = icon('arrow-right') - %span{ class: "ci-status-icon-#{build.status}" } - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - if build.retried? - %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } + .builds-container + - HasStatus::ORDERED_STATUSES.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } + = link_to project_job_path(@project, build) do + = icon('arrow-right') + %span{ class: "ci-status-icon-#{build.status}" } + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + - if build.retried? + %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 3bdb5d0adc4..20acd476f73 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -33,7 +33,7 @@ Suggestions: %code= 'gitlab' %code= @project.path # Path contains no spaces, but dashes - %code= @project.path_with_namespace + %code= @project.full_path %p Reserved: = link_to 'https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands', target: '__blank' do diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml index 8958b2cf5e1..9d5cebdda53 100644 --- a/app/views/projects/merge_requests/creations/_new_compare.html.haml +++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml @@ -41,7 +41,7 @@ - projects = target_projects(@project) .merge-request-select.dropdown = f.hidden_field :target_project_id - = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + = dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } .dropdown-menu.dropdown-menu-selectable.dropdown-target-project = dropdown_title("Select target project") = dropdown_filter("Search projects") diff --git a/app/views/projects/merge_requests/dropdowns/_project.html.haml b/app/views/projects/merge_requests/dropdowns/_project.html.haml index 25d5dc92f8a..aaf1ab00eeb 100644 --- a/app/views/projects/merge_requests/dropdowns/_project.html.haml +++ b/app/views/projects/merge_requests/dropdowns/_project.html.haml @@ -2,4 +2,4 @@ - projects.each do |project| %li %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } } - = project.path_with_namespace + = project.full_path diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index ea6cd16c7ad..d27e121beb4 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -17,6 +17,7 @@ -# haml-lint:disable InlineJavaScript :javascript + window.gl = window.gl || {}; window.gl.mrWidgetData = #{serialize_issuable(@merge_request)} #js-vue-mr-widget.mr-widget diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 25109f0f414..e3bbebbcf4c 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -17,8 +17,68 @@ - if import_sources_enabled? %p Create or Import your project from popular Git services - .col-lg-9 + .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| + .create-project-options + .first-column + .project-template + .form-group + = f.label :template_project, class: 'label-light' do + Create from template + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'} + %div + = render 'project_templates', f: f + .second-column + - if import_sources_enabled? + .project-import + .form-group.clearfix + = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + Import project from + .col-sm-12.import-buttons + %div + - if github_import_enabled? + = link_to new_import_github_path, class: 'btn import_github' do + = icon('github', text: 'GitHub') + %div + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do + = icon('bitbucket', text: 'Bitbucket') + - unless bitbucket_import_configured? + = render 'bitbucket_import_modal' + %div + - if gitlab_import_enabled? + = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do + = icon('gitlab', text: 'GitLab.com') + - unless gitlab_import_configured? + = render 'gitlab_import_modal' + %div + - if google_code_import_enabled? + = link_to new_import_google_code_path, class: 'btn import_google_code' do + = icon('google', text: 'Google Code') + %div + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + = icon('bug', text: 'Fogbugz') + %div + - if gitea_import_enabled? + = link_to new_import_gitea_url, class: 'btn import_gitea' do + = custom_icon('go_logo') + Gitea + %div + - if git_import_enabled? + %button.btn.js-toggle-button.import_git{ type: "button" } + = icon('git', text: 'Repo by URL') + .import_gitlab_project.has-tooltip{ data: { container: 'body' } } + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do + = icon('gitlab', text: 'GitLab export') + + .row + .col-lg-12 + .js-toggle-content.hide + %hr + = render "shared/import_form", f: f + %hr + .row .form-group.col-xs-12.col-sm-6 = f.label :namespace_id, class: 'label-light' do @@ -45,53 +105,6 @@ Want to house several dependent projects under the same namespace? = link_to "Create a group", new_group_path - - if import_sources_enabled? - .project-import.js-toggle-container - .form-group.clearfix - = f.label :visibility_level, class: 'label-light' do - Import project from - .col-sm-12.import-buttons - %div - - if github_import_enabled? - = link_to new_import_github_path, class: 'btn import_github' do - = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? - = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do - = icon('bitbucket', text: 'Bitbucket') - - unless bitbucket_import_configured? - = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do - = icon('gitlab', text: 'GitLab.com') - - unless gitlab_import_configured? - = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? - = link_to new_import_google_code_path, class: 'btn import_google_code' do - = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do - = icon('bug', text: 'FogBugz') - %div - - if gitea_import_enabled? - = link_to new_import_gitea_url, class: 'btn import_gitea' do - = custom_icon('go_logo') - Gitea - %div - - if git_import_enabled? - %button.btn.js-toggle-button.import_git{ type: "button" } - = icon('git', text: 'Repo by URL') - .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do - = icon('gitlab', text: 'GitLab export') - - .js-toggle-content.hide - = render "shared/import_form", f: f - .form-group = f.label :description, class: 'label-light' do Project description diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 7343d6e039c..bd8c38292d6 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -15,7 +15,7 @@ - else = s_("PipelineSchedules|None") %td.next-run-cell - - if pipeline_schedule.active? + - if pipeline_schedule.active? && pipeline_schedule.next_run_at = time_ago_with_tooltip(pipeline_schedule.real_next_run) - else = s_("PipelineSchedules|Inactive") diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index 95706888655..78dc4817ed7 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -3,4 +3,4 @@ %h4 Runner ##{@runner.id} %hr - = render 'form', runner: @runner, runner_form_url: runner_path(@runner) + = render 'form', runner: @runner, runner_form_url: runner_path(@runner) diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index ef3599460f1..5dbcbf7eba6 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -39,7 +39,7 @@ Suggestions: %code= 'gitlab' %code= @project.path # Path contains no spaces, but dashes - %code= @project.path_with_namespace + %code= @project.full_path .form-group = label_tag :request_url, 'Request URL', class: 'col-sm-2 col-xs-12 control-label' diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index 73b99453a4b..c31c95608c6 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -33,7 +33,7 @@ Suggestions: %code= 'gitlab' %code= @project.path # Path contains no spaces, but dashes - %code= @project.path_with_namespace + %code= @project.full_path .form-group = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label' diff --git a/app/views/projects/tree/_old_tree_content.html.haml b/app/views/projects/tree/_old_tree_content.html.haml new file mode 100644 index 00000000000..820b947804e --- /dev/null +++ b/app/views/projects/tree/_old_tree_content.html.haml @@ -0,0 +1,24 @@ +.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path } + .table-holder + %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } + %thead + %tr + %th= s_('ProjectFileTree|Name') + %th.hidden-xs + .pull-left= _('Last commit') + %th.text-right= _('Last Update') + - if @path.present? + %tr.tree-item + %td.tree-item-file-name + = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10' + %td + %td.hidden-xs + + = render_tree(tree) + + - if tree.readme + = render "projects/tree/readme", readme: tree.readme + +- if can_edit_tree? + = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post + = render 'projects/blob/new_dir' diff --git a/app/views/projects/tree/_old_tree_header.html.haml b/app/views/projects/tree/_old_tree_header.html.haml new file mode 100644 index 00000000000..13705ca303b --- /dev/null +++ b/app/views/projects/tree/_old_tree_header.html.haml @@ -0,0 +1,70 @@ +%ul.breadcrumb.repo-breadcrumb + %li + = link_to project_tree_path(@project, @ref) do + = @project.path + - path_breadcrumbs do |title, path| + %li + = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) + + - if current_user + %li + - if !on_top_of_branch? + %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } } + = icon('plus') + - else + %span.dropdown + %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown", "data-target" => ".add-to-tree-dropdown" } + = icon('plus') + .add-to-tree-dropdown + %ul.dropdown-menu + - if can_edit_tree? + %li + = link_to project_new_blob_path(@project, @id) do + = icon('pencil fw') + #{ _('New file') } + %li + = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do + = icon('file fw') + #{ _('Upload file') } + %li + = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do + = icon('folder fw') + #{ _('New directory') } + - elsif can?(current_user, :fork_project, @project) + %li + - continue_params = { to: project_new_blob_path(@project, @id), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('pencil fw') + #{ _('New file') } + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to upload a file again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('file fw') + #{ _('Upload file') } + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to create a new directory again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('folder fw') + #{ _('New directory') } + + %li.divider + %li + = link_to new_project_branch_path(@project) do + = icon('code-fork fw') + #{ _('New branch') } + %li + = link_to new_project_tag_path(@project) do + = icon('tags fw') + #{ _('New tag') } diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 820b947804e..a4bdd67209d 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -1,24 +1,5 @@ -.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path } - .table-holder - %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } - %thead - %tr - %th= s_('ProjectFileTree|Name') - %th.hidden-xs - .pull-left= _('Last commit') - %th.text-right= _('Last Update') - - if @path.present? - %tr.tree-item - %td.tree-item-file-name - = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10' - %td - %td.hidden-xs - - = render_tree(tree) - - - if tree.readme - = render "projects/tree/readme", readme: tree.readme - -- if can_edit_tree? - = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post - = render 'projects/blob/new_dir' +- content_url = local_assigns.fetch(:content_url, nil) +- if show_new_repo? + = render 'shared/repo/repo', project: @project, content_url: content_url +- else + = render 'projects/tree/old_tree_content', tree: tree diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 858418ff8df..427b059cb82 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -1,81 +1,19 @@ .tree-ref-container .tree-ref-holder = render 'shared/ref_switcher', destination: 'tree', path: @path + - if show_new_repo? + = icon('long-arrow-right', title: 'to target branch') + = render 'shared/target_switcher', destination: 'tree', path: @path - %ul.breadcrumb.repo-breadcrumb - %li - = link_to project_tree_path(@project, @ref) do - = @project.path - - path_breadcrumbs do |title, path| - %li - = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) - - - if current_user - %li - - if !on_top_of_branch? - %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } } - = icon('plus') - - else - %span.dropdown - %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown", "data-target" => ".add-to-tree-dropdown" } - = icon('plus') - .add-to-tree-dropdown - %ul.dropdown-menu - - if can_edit_tree? - %li - = link_to project_new_blob_path(@project, @id) do - = icon('pencil fw') - #{ _('New file') } - %li - = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do - = icon('file fw') - #{ _('Upload file') } - %li - = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do - = icon('folder fw') - #{ _('New directory') } - - elsif can?(current_user, :fork_project, @project) - %li - - continue_params = { to: project_new_blob_path(@project, @id), - notice: edit_in_new_fork_notice, - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - continue: continue_params) - = link_to fork_path, method: :post do - = icon('pencil fw') - #{ _('New file') } - %li - - continue_params = { to: request.fullpath, - notice: edit_in_new_fork_notice + " Try to upload a file again.", - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - continue: continue_params) - = link_to fork_path, method: :post do - = icon('file fw') - #{ _('Upload file') } - %li - - continue_params = { to: request.fullpath, - notice: edit_in_new_fork_notice + " Try to create a new directory again.", - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - continue: continue_params) - = link_to fork_path, method: :post do - = icon('folder fw') - #{ _('New directory') } - - %li.divider - %li - = link_to new_project_branch_path(@project) do - = icon('code-fork fw') - #{ _('New branch') } - %li - = link_to new_project_tag_path(@project) do - = icon('tags fw') - #{ _('New tag') } + - unless show_new_repo? + = render 'projects/tree/old_tree_header' .tree-controls - = render 'projects/find_file_link' + - if show_new_repo? + = render 'shared/repo/editable_mode' + - else + = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' - = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' + = render 'projects/find_file_link' = render 'projects/buttons/download', project: @project, ref: @ref diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index c8587245f88..375e6764add 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -5,8 +5,14 @@ - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") + +- if show_new_repo? + - content_for :page_specific_javascripts do + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'repo' + = render "projects/commits/head" %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } = render 'projects/last_push' - = render 'projects/files', commit: @last_commit, project: @project, ref: @ref + = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index adb8d5aaecb..e5a1fccf9ba 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -3,9 +3,12 @@ = form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f| = form_errors(@page) - = f.hidden_field :title, value: @page.title - if @page.persisted? = f.hidden_field :last_commit_sha, value: @page.last_commit_sha + + .form-group + .col-sm-12= f.label :title, class: 'control-label-full-width' + .col-sm-12= f.text_field :title, class: 'form-control', value: @page.title .form-group .col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12 diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index e71ce1f357f..f7283ae4739 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -1,21 +1,22 @@ %aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } } - .block.wiki-sidebar-header.append-bottom-default - %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } - = icon('angle-double-right') + .sidebar-container + .block.wiki-sidebar-header.append-bottom-default + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } + = icon('angle-double-right') - - git_access_url = project_wikis_git_access_path(@project) - = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do - = succeed ' ' do - = icon('cloud-download') - Clone repository + - git_access_url = project_wikis_git_access_path(@project) + = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do + = succeed ' ' do + = icon('cloud-download') + Clone repository - .blocks-container - .block.block-first - %ul.wiki-pages - = render @sidebar_wiki_entries, context: 'sidebar' + .blocks-container + .block.block-first + %ul.wiki-pages + = render @sidebar_wiki_entries, context: 'sidebar' - .block - = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do - More Pages + .block + = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do + More Pages = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index e64dd6085fe..e740fb93ea4 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -7,7 +7,7 @@ .git-access-header Clone repository - %strong= @project_wiki.path_with_namespace + %strong= @project_wiki.full_path = render "shared/clone_panel", project: @project_wiki diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index b4843eafdb7..3d9c90c38fe 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -11,7 +11,7 @@ %span = default_clone_protocol.upcase = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown + %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown %li = ssh_clone_button(project) %li diff --git a/app/views/shared/_commit_well.html.haml b/app/views/shared/_commit_well.html.haml new file mode 100644 index 00000000000..50e3d80a84d --- /dev/null +++ b/app/views/shared/_commit_well.html.haml @@ -0,0 +1,4 @@ +.info-well.hidden-xs.project-last-commit.append-bottom-default + .well-segment + %ul.blob-commit-info + = render 'projects/commits/commit', commit: commit, ref: ref, project: project diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 1c7c73be933..873179339dc 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -1,16 +1,16 @@ .form-group.import-url-data - = f.label :import_url, class: 'control-label' do + = f.label :import_url, class: 'label-light' do %span Git repository URL - .col-sm-10 - = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' - .well.prepend-top-20 - %ul - %li - The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>. - %li - If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>. - %li - The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. - %li - To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. + = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + + .well.prepend-top-20 + %ul + %li + The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>. + %li + If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>. + %li + The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. + %li + To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 5f3cdaefd54..96502d7ce93 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,6 +1,7 @@ -- if @projects.any? - .project-item-select-holder +- if any_projects?(@projects) + .project-item-select-holder.btn-group.pull-right + %a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } } + = icon('spinner spin') = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled] - %a.btn.btn-new.new-project-item-select-button - = local_assigns[:label] + %button.btn.btn-new.new-project-item-select-button = icon('caret-down') diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml new file mode 100644 index 00000000000..eb5ddb0dde4 --- /dev/null +++ b/app/views/shared/_sidebar_toggle_button.html.haml @@ -0,0 +1,8 @@ +%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" } + = icon('angle-double-left') + = icon('angle-double-right') + %span.collapse-text Collapse sidebar + += button_tag class: 'close-nav-button', type: 'button' do + = icon ('times') + %span.collapse-text Close sidebar diff --git a/app/views/shared/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml new file mode 100644 index 00000000000..3672b552f10 --- /dev/null +++ b/app/views/shared/_target_switcher.html.haml @@ -0,0 +1,20 @@ +- dropdown_toggle_text = @ref || @project.default_branch += form_tag nil, method: :get, class: "project-refs-target-form" do + = hidden_field_tag :destination, destination + - if defined?(path) + = hidden_field_tag :path, path + - @options && @options.each do |key, value| + = hidden_field_tag key, value, id: nil + .dropdown + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" } + %ul.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } + %li + = dropdown_title _("Create a new branch") + %li + = dropdown_input _("Create a new branch") + %li + = dropdown_title _("Select existing branch"), options: {close: false} + %li + = dropdown_filter _("Search branches and tags") + = dropdown_content + = dropdown_loading diff --git a/app/views/shared/icons/_abuse_reports.svg b/app/views/shared/icons/_abuse_reports.svg new file mode 100644 index 00000000000..fb16b269150 --- /dev/null +++ b/app/views/shared/icons/_abuse_reports.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg> diff --git a/app/views/shared/icons/_access_tokens.svg b/app/views/shared/icons/_access_tokens.svg new file mode 100644 index 00000000000..07ea6dab715 --- /dev/null +++ b/app/views/shared/icons/_access_tokens.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><path d="m13 2h-10c-1.7 0-3 1.3-3 3v6c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-6c0-1.7-1.3-3-3-3m1 9c0 .6-.4 1-1 1h-10c-.6 0-1-.4-1-1v-6c0-.6.4-1 1-1h10c.6 0 1 .4 1 1v6"/><circle cx="4" cy="8" r="1"/><circle cx="8" cy="8" r="1"/><circle cx="12" cy="8" r="1"/></svg> diff --git a/app/views/shared/icons/_account.svg b/app/views/shared/icons/_account.svg new file mode 100644 index 00000000000..d47e4f59914 --- /dev/null +++ b/app/views/shared/icons/_account.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><path d="m6.8 8c-.3 0-.5 0-.8 0-5 0-6 2.7-6 4.5s.1 2.5 6 2.5c.6 0 1.1 0 1.5 0-1-1.1-1.5-2.5-1.5-4 0-1.1.3-2.1.8-3"/><circle cx="6" cy="4" r="3"/><path d="m15.9 11.5l-.9-.6c0-.3-.1-.7-.2-.9l.6-.9c.1-.1.1-.2 0-.3l-.4-.5c-.1-.1-.2-.1-.3-.1l-.9.4c-.3-.2-.5-.3-.9-.4l-.3-1c0-.1-.1-.2-.2-.2h-.6c-.1 0-.2.1-.2.2l-.3 1c-.3.1-.6.2-.9.4l-1.1-.4c-.1 0-.2 0-.3.1l-.4.5c0 .1 0 .2 0 .3l.6.9c-.1.3-.2.6-.2.9l-.9.5c-.1.1-.1.2-.1.3l.1.6c0 .1.1.2.2.2l1.1.1c.1.2.3.4.5.6l-.2 1.2c0 .1 0 .2.1.3l.6.3c.1 0 .2 0 .3-.1l.9-.9c.2 0 .4 0 .6 0l.9.9c.1.1.2.1.3 0l.6-.3c.1 0 .2-.2.1-.3l-.1-1.1c.2-.2.4-.4.5-.6l1.1-.1c.1 0 .2-.1.2-.2l.1-.6c.1-.1.1-.2 0-.2m-3.9.5c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1"/></svg> diff --git a/app/views/shared/icons/_appearance.svg b/app/views/shared/icons/_appearance.svg new file mode 100644 index 00000000000..8ffeb780cb4 --- /dev/null +++ b/app/views/shared/icons/_appearance.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858-.411.022-.744.026-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023.172-.009.332-.02.478-.035-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg> diff --git a/app/views/shared/icons/_applications.svg b/app/views/shared/icons/_applications.svg new file mode 100644 index 00000000000..65442867174 --- /dev/null +++ b/app/views/shared/icons/_applications.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></svg> diff --git a/app/views/shared/icons/_authentication_log.svg b/app/views/shared/icons/_authentication_log.svg new file mode 100644 index 00000000000..0beb84c2912 --- /dev/null +++ b/app/views/shared/icons/_authentication_log.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></svg> diff --git a/app/views/shared/icons/_chat.svg b/app/views/shared/icons/_chat.svg new file mode 100644 index 00000000000..0c474c9f980 --- /dev/null +++ b/app/views/shared/icons/_chat.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M5.414 12l-3.707 3.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></svg> diff --git a/app/views/shared/icons/_container_registry.svg b/app/views/shared/icons/_container_registry.svg new file mode 100644 index 00000000000..56d62aab670 --- /dev/null +++ b/app/views/shared/icons/_container_registry.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m16 11.764v-8.764c0-1.657-1.343-3-3-3h-10c-1.657 0-3 1.343-3 3v8.764c.531-.475 1.232-.764 2-.764v-8c0-.552.448-1 1-1h10c.552 0 1 .448 1 1v8c.768 0 1.469.289 2 .764m-14 .236h12c1.105 0 2 .895 2 2 0 1.105-.895 2-2 2h-12c-1.105 0-2-.895-2-2 0-1.105.895-2 2-2m10 1c-.552 0-1 .448-1 1 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-.552-.448-1-1-1"/></svg> diff --git a/app/views/shared/icons/_doc_text.svg b/app/views/shared/icons/_doc_text.svg new file mode 100644 index 00000000000..92902a5b449 --- /dev/null +++ b/app/views/shared/icons/_doc_text.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></svg> diff --git a/app/views/shared/icons/_emails.svg b/app/views/shared/icons/_emails.svg new file mode 100644 index 00000000000..3ebc64bb03e --- /dev/null +++ b/app/views/shared/icons/_emails.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm0-2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3z"/><path d="M3.212 4L8 8.31 12.788 4H3.212zm6.126 5.796a2 2 0 0 1-2.676 0L.183 3.965A3.001 3.001 0 0 1 3 2h10c1.293 0 2.395.818 2.817 1.965l-6.48 5.83z"/></svg> diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg new file mode 100644 index 00000000000..439023c86d0 --- /dev/null +++ b/app/views/shared/icons/_issues.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></svg> diff --git a/app/views/shared/icons/_issues.svg.erb b/app/views/shared/icons/_issues.svg.erb deleted file mode 100644 index fa8655b5609..00000000000 --- a/app/views/shared/icons/_issues.svg.erb +++ /dev/null @@ -1,4 +0,0 @@ -<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16" class="gitlab-icon"> - <path fill="#7E7C7C" d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2"></path> - <path fill="#7E7C7C" d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z"></path> -</svg> diff --git a/app/views/shared/icons/_java_spring.svg b/app/views/shared/icons/_java_spring.svg new file mode 100644 index 00000000000..508349aa456 --- /dev/null +++ b/app/views/shared/icons/_java_spring.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring"> + <g fill="none" fill-rule="evenodd"> + <rect width="32" height="32"/> + <path fill="#70AD51" d="M5.46647617,27.9932117 C6.0517027,28.4658996 6.91159892,28.3777063 7.38425926,27.7914452 C7.85922261,27.2048452 7.76991326,26.3449044 7.18398981,25.8699411 C6.59874295,25.3956543 5.74015536,25.4869934 5.26383884,26.0722403 C4.81393367,26.6267596 4.87238621,27.4284565 5.37913494,27.9159868 L5.11431334,27.6818383 C1.97157151,24.7616933 0,20.5966301 0,15.9782542 C0,7.16842834 7.16775175,0 15.9796074,0 C20.4586065,0 24.5113565,1.8565519 27.4145869,4.8362365 C28.0749348,3.93840692 28.6466499,2.93435335 29.115524,1.82069284 C31.1513712,7.93770658 32.3482517,13.0811131 31.909824,17.1311567 C31.3178113,25.4044499 24.4017495,31.9585382 15.9796074,31.9585382 C12.0682639,31.9585382 8.48438805,30.5444735 5.7042963,28.2034861 L5.46647617,27.9932117 Z M29.0471888,23.0106888 C33.0546075,17.6737787 30.8211972,9.04527781 28.9612624,3.529749 C27.3029502,6.98304378 23.2217836,9.62375882 19.6981239,10.4613722 C16.3950312,11.2482417 13.4715032,10.6021021 10.4153644,11.7780085 C3.44517575,14.457289 3.55613585,22.7698242 7.39373146,24.6365249 C7.39711439,24.6392312 7.62444728,24.7616933 7.62174094,24.7576338 C7.62309411,24.7562806 13.2658211,23.6358542 16.3862356,22.4843049 C20.9450718,20.7996058 25.9524846,16.6494275 27.5986182,11.8273993 C26.723116,16.8415779 22.4179995,21.6669891 18.093262,23.8828081 C15.7908399,25.0648038 14.0005934,25.3279957 10.2123886,26.6385428 C9.74892722,26.798217 9.38492397,26.9538318 9.38492397,26.9538318 C10.3463526,26.7948341 11.301692,26.7420604 11.301692,26.7420604 C16.6954354,26.4869875 25.1087819,28.2582896 29.0471888,23.0106888 Z"/> + </g> +</svg> diff --git a/app/views/shared/icons/_key.svg b/app/views/shared/icons/_key.svg new file mode 100644 index 00000000000..5ad03ed4480 --- /dev/null +++ b/app/views/shared/icons/_key.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M7.574 6.689a4.002 4.002 0 0 1 6.275-4.861 4 4 0 0 1-4.86 6.275l-2.21 2.21.706.707a1 1 0 0 1-1.414 1.415l-.707-.708-.707.708.707.707a1 1 0 0 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.415-1.414l5.746-5.746zm2.033-.618a2 2 0 1 0 2.828-2.829 2 2 0 0 0-2.828 2.829z"/></svg> diff --git a/app/views/shared/icons/_key_2.svg b/app/views/shared/icons/_key_2.svg new file mode 100644 index 00000000000..368b2876c60 --- /dev/null +++ b/app/views/shared/icons/_key_2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></svg> diff --git a/app/views/shared/icons/_labels.svg b/app/views/shared/icons/_labels.svg new file mode 100644 index 00000000000..1ebad4bb4fa --- /dev/null +++ b/app/views/shared/icons/_labels.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667z"/><path d="M.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg> diff --git a/app/views/shared/icons/_lock.svg b/app/views/shared/icons/_lock.svg new file mode 100644 index 00000000000..703c09611a3 --- /dev/null +++ b/app/views/shared/icons/_lock.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><path d="m8 9c-.6 0-1 .4-1 1v1c0 .6.4 1 1 1s1-.4 1-1v-1c0-.6-.4-1-1-1"/><path d="m12 5v-1c0-2.2-1.8-4-4-4s-4 1.8-4 4v1c-1.7 0-3 1.3-3 3v5c0 1.7 1.3 3 3 3h8c1.7 0 3-1.3 3-3v-5c0-1.7-1.3-3-3-3m-6-1c0-1.1.9-2 2-2s2 .9 2 2v1h-4v-1m7 9c0 .6-.4 1-1 1h-8c-.6 0-1-.4-1-1v-5c0-.6.4-1 1-1h8c.6 0 1 .4 1 1v5"/></svg> diff --git a/app/views/shared/icons/_members.svg b/app/views/shared/icons/_members.svg new file mode 100644 index 00000000000..68d957d6d11 --- /dev/null +++ b/app/views/shared/icons/_members.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></svg> diff --git a/app/views/shared/icons/_messages.svg b/app/views/shared/icons/_messages.svg new file mode 100644 index 00000000000..9a2ea15c35d --- /dev/null +++ b/app/views/shared/icons/_messages.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></svg> diff --git a/app/views/shared/icons/_monitoring.svg b/app/views/shared/icons/_monitoring.svg new file mode 100644 index 00000000000..21689b0877c --- /dev/null +++ b/app/views/shared/icons/_monitoring.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></svg> diff --git a/app/views/shared/icons/_node_express.svg b/app/views/shared/icons/_node_express.svg new file mode 100644 index 00000000000..f2c94319f19 --- /dev/null +++ b/app/views/shared/icons/_node_express.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express"> + <g fill="none" fill-rule="evenodd" transform="translate(-3)"> + <rect width="32" height="32"/> + <path fill="#353535" d="M4.19170065,16.2667139 C4.23142421,18.3323387 4.47969269,20.2489714 4.93651356,22.0166696 C5.39333443,23.7843677 6.09841693,25.3236323 7.05178222,26.6345096 C8.00514751,27.9453869 9.23655921,28.9781838 10.7460543,29.7329313 C12.2555493,30.4876788 14.1026668,30.8650469 16.2874623,30.8650469 C19.5050701,30.8650469 22.1764391,30.0209341 24.3016492,28.3326831 C26.4268593,26.644432 27.7476477,24.1120935 28.2640539,20.7355914 L29.4557545,20.7355914 C29.0187954,24.3107112 27.6086304,27.0813875 25.2252172,29.0477034 C22.841804,31.0140194 19.9023051,31.9971626 16.4066324,31.9971626 C14.0232191,32.0368861 11.9874175,31.659518 10.2991665,30.8650469 C8.61091547,30.0705759 7.23054269,28.9484023 6.15800673,27.4984926 C5.08547078,26.0485829 4.29101162,24.3404957 3.77460543,22.3741798 C3.25819923,20.4078639 3,18.2926164 3,16.0283738 C3,13.4860664 3.3773681,11.2218578 4.13211562,9.23568007 C4.88686314,7.24950238 5.87993709,5.57120741 7.11136726,4.20074481 C8.34279742,2.8302822 9.77282391,1.78755456 11.4014896,1.07253059 C13.0301553,0.357506621 14.6985195,0 16.4066324,0 C18.7900456,0 20.8457087,0.456814016 22.5736832,1.37045575 C24.3016578,2.28409749 25.7118228,3.4956477 26.8042206,5.00514275 C27.8966183,6.51463779 28.6910775,8.24258646 29.1876219,10.1890406 C29.6841663,12.1354947 29.8927118,14.1613656 29.8132647,16.2667139 L4.19170065,16.2667139 Z M28.6215641,15.0750133 C28.6215641,13.2080062 28.3633648,11.4304039 27.8469586,9.74215285 C27.3305524,8.05390181 26.5658855,6.57422163 25.5529349,5.30306791 C24.5399843,4.03191419 23.2787803,3.0289095 21.7692853,2.29402376 C20.2597903,1.55913801 18.5119801,1.19170065 16.5258024,1.19170065 C14.8574132,1.19170065 13.2982871,1.50948432 11.8483774,2.14506118 C10.3984676,2.78063804 9.12733299,3.70419681 8.03493526,4.9157652 C6.94253754,6.12733359 6.05870172,7.58715229 5.38340131,9.2952651 C4.70810089,11.0033779 4.31087132,12.9299414 4.19170065,15.0750133 L28.6215641,15.0750133 Z"/> + </g> +</svg> diff --git a/app/views/shared/icons/_notifications.svg b/app/views/shared/icons/_notifications.svg new file mode 100644 index 00000000000..da55de041da --- /dev/null +++ b/app/views/shared/icons/_notifications.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></svg> diff --git a/app/views/shared/icons/_overview.svg b/app/views/shared/icons/_overview.svg new file mode 100644 index 00000000000..4791282df7f --- /dev/null +++ b/app/views/shared/icons/_overview.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M2 2v3h3V2H2zm0-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm9 2v3h3V2h-3zm0-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zM2 11v3h3v-3H2zm0-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm9 2v3h3v-3h-3zm0-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2z"/></svg> diff --git a/app/views/shared/icons/_pipeline.svg b/app/views/shared/icons/_pipeline.svg new file mode 100644 index 00000000000..5bedc96a1bd --- /dev/null +++ b/app/views/shared/icons/_pipeline.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><path d="m8 0c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8m0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6"/><circle cx="12.5" cy="9.5" r=".5"/><circle cx="12.5" cy="6.5" r=".5"/><circle cx="10.5" cy="12.5" r=".5"/><circle cx="10.5" cy="3.5" r=".5"/><circle cx="5.5" cy="12.5" r=".5"/><circle cx="5.5" cy="3.5" r=".5"/><circle cx="3.5" cy="9.5" r=".5"/><circle cx="3.5" cy="6.5" r=".5"/><path d="m9 7.2c0 0 0-.1 0-.2v-1.9c0-.1 0-.1-.1-.2l-.8-.8c0 0-.1 0-.1 0l-.9.8c-.1.1-.1.1-.1.2v1.9c0 .1 0 .2 0 .2-.6.4-1 1-1 1.8 0 1.1.9 2 2 2s2-.9 2-2c0-.8-.4-1.4-1-1.8m-1 2.8c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1"/></svg> diff --git a/app/views/shared/icons/_preferences.svg b/app/views/shared/icons/_preferences.svg new file mode 100644 index 00000000000..cbd7a4fe9f0 --- /dev/null +++ b/app/views/shared/icons/_preferences.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2zm10 5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zm-5 5h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2z" fill-rule="evenodd"/></svg> diff --git a/app/views/shared/icons/_profile.svg b/app/views/shared/icons/_profile.svg new file mode 100644 index 00000000000..29e360a9051 --- /dev/null +++ b/app/views/shared/icons/_profile.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg> diff --git a/app/views/shared/icons/_project.svg b/app/views/shared/icons/_project.svg new file mode 100644 index 00000000000..bbfdd939e7b --- /dev/null +++ b/app/views/shared/icons/_project.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></svg> diff --git a/app/views/shared/icons/_project.svg.erb b/app/views/shared/icons/_project.svg.erb deleted file mode 100644 index 2f60bb7245e..00000000000 --- a/app/views/shared/icons/_project.svg.erb +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16"> - <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E" fill-rule="evenodd"></path> -</svg> diff --git a/app/views/shared/icons/_rails.svg b/app/views/shared/icons/_rails.svg new file mode 100644 index 00000000000..0bb09a705df --- /dev/null +++ b/app/views/shared/icons/_rails.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails"> + <g fill="none" fill-rule="evenodd" transform="translate(0 -6)"> + <rect width="32" height="32"/> + <path fill="#C00" fill-rule="nonzero" d="M0.984615385,25.636044 C0.984615385,25.636044 1.40659341,21.4725275 4.36043956,16.5494505 C7.31428571,11.6263736 12.3498901,7.8989011 16.4430769,7.53318681 C24.5872527,6.71736264 31.9015385,14.0175824 31.9015385,14.0175824 C31.9015385,14.0175824 31.6624176,14.1863736 31.4092308,14.3973626 C23.4197802,8.48967033 18.5389011,11.2747253 17.0057143,12.0202198 C9.97274725,15.9446154 12.0967033,25.636044 12.0967033,25.636044 L0.984615385,25.636044 Z M24.1371429,8.32087912 C23.687033,8.13802198 23.2369231,7.96923077 22.7727473,7.81450549 L22.829011,6.88615385 C23.7151648,7.13934066 24.0668132,7.30813187 24.1934066,7.37846154 L24.1371429,8.32087912 Z M22.8008791,11.3028571 C23.250989,11.330989 23.7151648,11.3872527 24.1934066,11.4857143 L24.1371429,12.3578022 C23.672967,12.2593407 23.2087912,12.2030769 22.7446154,12.189011 L22.8008791,11.3028571 Z M17.5964835,6.91428571 C17.1885714,6.91428571 16.7806593,6.92835165 16.3727473,6.97054945 L16.1054945,6.14065934 C16.5696703,6.0843956 17.0197802,6.05626374 17.4558242,6.05626374 L17.7371429,6.91428571 C17.6949451,6.91428571 17.6386813,6.91428571 17.5964835,6.91428571 Z M18.2716484,12.0905495 C18.6232967,11.9358242 19.0312088,11.7810989 19.5094505,11.6404396 L19.8189011,12.5687912 C19.410989,12.6953846 19.0030769,12.8641758 18.5951648,13.0610989 L18.2716484,12.0905495 Z M11.8857143,8.39120879 C11.52,8.57406593 11.1683516,8.78505495 10.8026374,9.01010989 L10.1556044,8.02549451 C10.5353846,7.80043956 10.9010989,7.60351648 11.2527473,7.42065934 L11.8857143,8.39120879 Z M14.7692308,14.7208791 C15.0224176,14.3973626 15.3178022,14.0738462 15.6413187,13.7784615 L16.2742857,14.7349451 C15.9648352,15.0584615 15.6835165,15.381978 15.4443956,15.7336264 L14.7692308,14.7208791 Z M12.7296703,19.2501099 C12.8421978,18.7437363 12.9687912,18.2232967 13.1516484,17.7028571 L14.1643956,18.5046154 C14.0237363,19.0531868 13.9252747,19.6017582 13.869011,20.1503297 L12.7296703,19.2501099 Z M6.56879121,12.5687912 C6.23120879,12.9204396 5.90769231,13.3002198 5.61230769,13.68 L4.52923077,12.7516484 C4.85274725,12.4 5.2043956,12.0483516 5.57010989,11.6967033 L6.56879121,12.5687912 Z M2.32087912,18.8562637 C2.09582418,19.3767033 1.80043956,20.0659341 1.61758242,20.5441758 L0,19.9534066 C0.140659341,19.5736264 0.436043956,18.8703297 0.703296703,18.2654945 L2.32087912,18.8562637 Z M12.5186813,22.8228571 L14.0378022,23.3714286 C14.1221978,24.0325275 14.2487912,24.6514286 14.3753846,25.2 L12.6874725,24.5951648 C12.6171429,24.1731868 12.5468132,23.5683516 12.5186813,22.8228571 Z"/> + </g> +</svg> diff --git a/app/views/shared/icons/_service_templates.svg b/app/views/shared/icons/_service_templates.svg new file mode 100644 index 00000000000..b65cd8300b2 --- /dev/null +++ b/app/views/shared/icons/_service_templates.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></svg> diff --git a/app/views/shared/icons/_settings.svg b/app/views/shared/icons/_settings.svg new file mode 100644 index 00000000000..96c5ef8c04d --- /dev/null +++ b/app/views/shared/icons/_settings.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918c.594.181 1.15.452 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></svg> diff --git a/app/views/shared/icons/_snippets.svg b/app/views/shared/icons/_snippets.svg new file mode 100644 index 00000000000..1e1340187b4 --- /dev/null +++ b/app/views/shared/icons/_snippets.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 1 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 1 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></svg> diff --git a/app/views/shared/icons/_spam_logs.svg b/app/views/shared/icons/_spam_logs.svg new file mode 100644 index 00000000000..80ee0eb3856 --- /dev/null +++ b/app/views/shared/icons/_spam_logs.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg> diff --git a/app/views/shared/icons/_system_hooks.svg b/app/views/shared/icons/_system_hooks.svg new file mode 100644 index 00000000000..7b95a6f29f3 --- /dev/null +++ b/app/views/shared/icons/_system_hooks.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></svg> diff --git a/app/views/shared/icons/_wiki.svg b/app/views/shared/icons/_wiki.svg new file mode 100644 index 00000000000..b5ad38d9863 --- /dev/null +++ b/app/views/shared/icons/_wiki.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 8 6.191V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></svg> diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index bd66f39fa59..0a692d9653f 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -1,5 +1,5 @@ .dropdown-page-two.dropdown-new-label - = dropdown_title("Create new label", back: true) + = dropdown_title("Create new label", options: { back: true }) = dropdown_content do .dropdown-labels-error.js-label-error %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 3428d6e0445..1ad00461d76 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,5 +1,6 @@ - type = local_assigns.fetch(:type) - block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' +- full_path = @project.present? ? @project.full_path : @group.full_path .issues-filters .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } @@ -18,7 +19,7 @@ dropdown_class: "filtered-search-history-dropdown", content_class: "filtered-search-history-dropdown-content", title: "Recent searches" }) do - .js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } } + .js-filtered-search-history-dropdown{ data: { full_path: full_path } } .filtered-search-box-input-container.droplab-dropdown .scroll-container %ul.tokens-container.list-unstyled diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index e7510c1d1ec..c2de6926460 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -115,6 +115,10 @@ - if can? current_user, :admin_label, @project and @project = render partial: "shared/issuable/label_page_create" + - if issuable.has_attribute?(:confidential) + %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe + #js-confidential-entry-point + = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user - subscribed = issuable.subscribed?(current_user, @project) diff --git a/app/views/shared/issuable/_user_dropdown_item.html.haml b/app/views/shared/issuable/_user_dropdown_item.html.haml index a82c01c6dc2..c18e4975bb8 100644 --- a/app/views/shared/issuable/_user_dropdown_item.html.haml +++ b/app/views/shared/issuable/_user_dropdown_item.html.haml @@ -3,7 +3,8 @@ %li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) } %button.btn.btn-link.dropdown-user{ type: :button } - = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 30) + .avatar-container.s40 + = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40).gsub('/images/{{avatar_url}}','{{avatar_url}}').html_safe .dropdown-user-details %span = user.name diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 895fb8247b5..66ac8196f2f 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -67,7 +67,7 @@ .block.issues .sidebar-collapsed-icon %strong - = icon('hashtag', 'aria-hidden': 'true') + = custom_icon('issues') %span= milestone.issues_visible_to_user(current_user).count .title.hide-collapsed Issues diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 7ed6c622558..914506bf0ce 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -10,7 +10,7 @@ - load_pipeline_status(projects) .js-projects-list-holder - - if projects.any? + - if any_projects?(projects) %ul.projects-list - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil @@ -22,7 +22,7 @@ %li.project-row.private-forks-notice = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') %strong= pluralize(@private_forks_count, 'private fork') - %span you have no access to. + %span you have no access to. = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages - else .nothing-here-block No projects found diff --git a/app/views/shared/repo/_editable_mode.html.haml b/app/views/shared/repo/_editable_mode.html.haml new file mode 100644 index 00000000000..73fdb8b523f --- /dev/null +++ b/app/views/shared/repo/_editable_mode.html.haml @@ -0,0 +1,2 @@ +.editable-mode + %repo-edit-button diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml new file mode 100644 index 00000000000..0fc40cf0801 --- /dev/null +++ b/app/views/shared/repo/_repo.html.haml @@ -0,0 +1,2 @@ +#repo{ data: { url: content_url, project_name: project.name, refs_url: refs_project_path(project, format: :json), project_url: project_path(project), project_id: project.id, can_commit: (!!can_push_branch?(project, @ref)).to_s } } + %repo diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml index 00788e77b6b..093b2d82813 100644 --- a/app/views/u2f/_register.html.haml +++ b/app/views/u2f/_register.html.haml @@ -37,7 +37,3 @@ .col-md-3 = hidden_field_tag 'u2f_registration[device_response]', nil, class: 'form-control', required: true, id: "js-device-response" = submit_tag "Register U2F device", class: "btn btn-success" - -:javascript - var u2fRegister = new U2FRegister($("#js-register-u2f"), gon.u2f); - u2fRegister.start(); diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index 805a346a85e..6b1d75c6e72 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -1,6 +1,6 @@ %h4.prepend-top-20 Contributions for - %strong= @calendar_date.to_s(:short) + %strong= @calendar_date.to_s(:medium) - if @events.any? %ul.bordered-list @@ -8,7 +8,7 @@ %li %span.light %i.fa.fa-clock-o - = event.created_at.to_s(:time) + = event.created_at.strftime('%-I:%M%P') - if event.push? #{event.action_name} #{event.ref_type} %strong @@ -30,4 +30,4 @@ = event.project_name - else %p - No contributions found for #{@calendar_date.to_s(:short)} + No contributions found for #{@calendar_date.to_s(:medium)} diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index a449706c567..879e0f99b14 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -104,7 +104,7 @@ .tab-content #activity.tab-pane .row-content-block.calender-block.white.second-block.hidden-xs - .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path } } + .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } %h4.center.light %i.fa.fa-spinner.fa-spin .user-calendar-activities diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb new file mode 100644 index 00000000000..eb0d6c9c36c --- /dev/null +++ b/app/workers/concerns/new_issuable.rb @@ -0,0 +1,26 @@ +module NewIssuable + attr_reader :issuable, :user + + def objects_found?(issuable_id, user_id) + set_user(user_id) + set_issuable(issuable_id) + + user && issuable + end + + def set_user(user_id) + @user = User.find_by(id: user_id) + + log_error(User, user_id) unless @user + end + + def set_issuable(issuable_id) + @issuable = issuable_class.find_by(id: issuable_id) + + log_error(issuable_class, issuable_id) unless @issuable + end + + def log_error(record_class, record_id) + Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job") + end +end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index d3f7e479a8d..1afa24c8e2a 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -31,8 +31,6 @@ class EmailReceiverWorker when Gitlab::Email::EmptyEmailError can_retry = true "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." - when Gitlab::Email::AutoGeneratedEmailError - "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." when Gitlab::Email::UserNotFoundError "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." when Gitlab::Email::UserBlockedError diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 22f67fa9e9f..3dd14466994 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -66,7 +66,7 @@ class IrkerWorker end def send_new_branch(project, repo_name, committer, branch) - repo_path = project.path_with_namespace + repo_path = project.full_path newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches" newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors @@ -109,7 +109,7 @@ class IrkerWorker end def send_commits_count(data, project, repo, committer, branch) - url = compare_url data, project.path_with_namespace + url = compare_url data, project.full_path commits = colorize_commits data['total_commits_count'] new_commits = 'new commit' diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index 48e2da338f6..c3b58df92c1 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -7,6 +7,8 @@ class MergeWorker current_user = User.find(current_user_id) merge_request = MergeRequest.find(merge_request_id) + merge_request.update_column(:merge_jid, jid) + MergeRequests::MergeService.new(merge_request.target_project, current_user, params) .execute(merge_request) end diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb new file mode 100644 index 00000000000..d9a8e892e90 --- /dev/null +++ b/app/workers/new_issue_worker.rb @@ -0,0 +1,17 @@ +class NewIssueWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + include NewIssuable + + def perform(issue_id, user_id) + return unless objects_found?(issue_id, user_id) + + EventCreateService.new.open_issue(issuable, user) + NotificationService.new.new_issue(issuable, user) + issuable.create_cross_references!(user) + end + + def issuable_class + Issue + end +end diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb new file mode 100644 index 00000000000..1910c490159 --- /dev/null +++ b/app/workers/new_merge_request_worker.rb @@ -0,0 +1,17 @@ +class NewMergeRequestWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + include NewIssuable + + def perform(merge_request_id, user_id) + return unless objects_found?(merge_request_id, user_id) + + EventCreateService.new.open_mr(issuable, user) + NotificationService.new.new_merge_request(issuable, user) + issuable.create_cross_references!(user) + end + + def issuable_class + MergeRequest + end +end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 4eeb9666bb0..64788da7299 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -4,7 +4,7 @@ class PagesWorker sidekiq_options queue: :pages, retry: false def perform(action, *arg) - send(action, *arg) + send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend end def deploy(build_id) diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 625476b7e01..6be541abd3e 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -16,7 +16,7 @@ class RepositoryImportWorker Gitlab::Metrics.add_event(:import_repository, import_url: @project.import_url, - path: @project.path_with_namespace) + path: @project.full_path) project.update_columns(import_jid: self.jid, import_error: nil) diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb new file mode 100644 index 00000000000..7843179d77c --- /dev/null +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -0,0 +1,34 @@ +class StuckMergeJobsWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + stuck_merge_requests.find_in_batches(batch_size: 100) do |group| + jids = group.map(&:merge_jid) + + # Find the jobs that aren't currently running or that exceeded the threshold. + completed_jids = Gitlab::SidekiqStatus.completed_jids(jids) + + if completed_jids.any? + completed_ids = group.select { |merge_request| completed_jids.include?(merge_request.merge_jid) }.map(&:id) + + apply_current_state!(completed_jids, completed_ids) + end + end + end + + private + + def apply_current_state!(completed_jids, completed_ids) + merge_requests = MergeRequest.where(id: completed_ids) + + merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) + merge_requests.where(merge_commit_sha: nil).update_all(state: :opened) + + Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") + end + + def stuck_merge_requests + MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil) + end +end diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof index df79feb201d..810863ea4a0 100755 --- a/bin/rspec-stackprof +++ b/bin/rspec-stackprof @@ -1,5 +1,6 @@ #!/usr/bin/env ruby +require 'bundler/setup' require 'stackprof' $:.unshift 'spec' require 'rails_helper' @@ -13,4 +14,4 @@ StackProf.run(mode: :wall, out: output_file, interval: interval) do RSpec::Core::Runner.run(ARGV, $stderr, $stdout) end -system("stackprof #{output_file} --text --limit #{limit}") +system("bundle exec stackprof #{output_file} --text --limit #{limit}") diff --git a/changelogs/unreleased/13247-api_project_events_target_iid.yml b/changelogs/unreleased/13247-api_project_events_target_iid.yml new file mode 100644 index 00000000000..08a31039f77 --- /dev/null +++ b/changelogs/unreleased/13247-api_project_events_target_iid.yml @@ -0,0 +1,4 @@ +--- +title: Expose target_iid in Events API +merge_request: 13247 +author: sue445 diff --git a/changelogs/unreleased/13265-project_events_noteable_iid.yml b/changelogs/unreleased/13265-project_events_noteable_iid.yml new file mode 100644 index 00000000000..54d538bb548 --- /dev/null +++ b/changelogs/unreleased/13265-project_events_noteable_iid.yml @@ -0,0 +1,4 @@ +--- +title: Expose noteable_iid in Note +merge_request: 13265 +author: sue445 diff --git a/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml b/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml new file mode 100644 index 00000000000..1b3c3b8538d --- /dev/null +++ b/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml @@ -0,0 +1,4 @@ +--- +title: Fix timezone inconsistencies in user contribution graph +merge_request: 13208 +author: diff --git a/changelogs/unreleased/28472-ignore-auto-generated-mails.yml b/changelogs/unreleased/28472-ignore-auto-generated-mails.yml new file mode 100644 index 00000000000..af63b43e62e --- /dev/null +++ b/changelogs/unreleased/28472-ignore-auto-generated-mails.yml @@ -0,0 +1,4 @@ +--- +title: Don't send rejection mails for all auto-generated mails +merge_request: 13254 +author: diff --git a/changelogs/unreleased/31207-clean-locked-merge-requests.yml b/changelogs/unreleased/31207-clean-locked-merge-requests.yml new file mode 100644 index 00000000000..1f52987baef --- /dev/null +++ b/changelogs/unreleased/31207-clean-locked-merge-requests.yml @@ -0,0 +1,4 @@ +--- +title: Unlock stuck merge request and set the proper state +merge_request: 13207 +author: diff --git a/changelogs/unreleased/32844-issuables-performance.yml b/changelogs/unreleased/32844-issuables-performance.yml new file mode 100644 index 00000000000..e9b21c1aa45 --- /dev/null +++ b/changelogs/unreleased/32844-issuables-performance.yml @@ -0,0 +1,4 @@ +--- +title: Move some code from services to workers in order to improve performance +merge_request: 13326 +author: diff --git a/changelogs/unreleased/33095-mr-widget-ui.yml b/changelogs/unreleased/33095-mr-widget-ui.yml new file mode 100644 index 00000000000..9ce3086df27 --- /dev/null +++ b/changelogs/unreleased/33095-mr-widget-ui.yml @@ -0,0 +1,4 @@ +--- +title: clean up merge request widget UI +merge_request: +author: diff --git a/changelogs/unreleased/33620-remove-events-from-notification_settings.yml b/changelogs/unreleased/33620-remove-events-from-notification_settings.yml new file mode 100644 index 00000000000..f5f3ef3fb82 --- /dev/null +++ b/changelogs/unreleased/33620-remove-events-from-notification_settings.yml @@ -0,0 +1,4 @@ +--- +title: Remove events column from notification settings table +merge_request: +author: diff --git a/changelogs/unreleased/33874_confi.yml b/changelogs/unreleased/33874_confi.yml new file mode 100644 index 00000000000..940753d9aaa --- /dev/null +++ b/changelogs/unreleased/33874_confi.yml @@ -0,0 +1,5 @@ +--- +title: Update confidential issue UI - add confidential visibility and settings to + sidebar +merge_request: +author: diff --git a/changelogs/unreleased/34027-add-icons-to-sidebar.yml b/changelogs/unreleased/34027-add-icons-to-sidebar.yml new file mode 100644 index 00000000000..f5b50ca1dee --- /dev/null +++ b/changelogs/unreleased/34027-add-icons-to-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Add icons to contextual sidebars +merge_request: +author: diff --git a/changelogs/unreleased/34028-collapse-sidebar.yml b/changelogs/unreleased/34028-collapse-sidebar.yml new file mode 100644 index 00000000000..468212240ac --- /dev/null +++ b/changelogs/unreleased/34028-collapse-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Make contextual sidebar collapsible +merge_request: +author: diff --git a/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml b/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml new file mode 100644 index 00000000000..13f28da8577 --- /dev/null +++ b/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml @@ -0,0 +1,4 @@ +--- +title: Use full path of user's avatar in webhooks +merge_request: 13401 +author: Vitaliy @blackst0ne Klachkov diff --git a/changelogs/unreleased/34492-firefox-job.yml b/changelogs/unreleased/34492-firefox-job.yml new file mode 100644 index 00000000000..881b8f649ea --- /dev/null +++ b/changelogs/unreleased/34492-firefox-job.yml @@ -0,0 +1,4 @@ +--- +title: Use jQuery to control scroll behavior in job log for cross browser consistency +merge_request: +author: diff --git a/changelogs/unreleased/34519-extend-api-group-secret-variable.yml b/changelogs/unreleased/34519-extend-api-group-secret-variable.yml new file mode 100644 index 00000000000..e0b625c392f --- /dev/null +++ b/changelogs/unreleased/34519-extend-api-group-secret-variable.yml @@ -0,0 +1,4 @@ +--- +title: Extend API for Group Secret Variable +merge_request: 12936 +author: diff --git a/changelogs/unreleased/34764-rename-to-overview.yml b/changelogs/unreleased/34764-rename-to-overview.yml new file mode 100644 index 00000000000..5b9643285b7 --- /dev/null +++ b/changelogs/unreleased/34764-rename-to-overview.yml @@ -0,0 +1,4 @@ +--- +title: Rename about to overview for group and project page +merge_request: +author: diff --git a/changelogs/unreleased/34869-bump-rubocop-to-0-49-1-and-rubocop-rspec-to-1-15-1.yml b/changelogs/unreleased/34869-bump-rubocop-to-0-49-1-and-rubocop-rspec-to-1-15-1.yml new file mode 100644 index 00000000000..0eb2d069719 --- /dev/null +++ b/changelogs/unreleased/34869-bump-rubocop-to-0-49-1-and-rubocop-rspec-to-1-15-1.yml @@ -0,0 +1,4 @@ +--- +title: Bump rubocop to 0.49.1 and rubocop-rspec to 1.15.1 +merge_request: +author: Takuya Noguchi diff --git a/changelogs/unreleased/35052-please-select-a-file-when-attempting-to-upload-or-replace-from-the-ui.yml b/changelogs/unreleased/35052-please-select-a-file-when-attempting-to-upload-or-replace-from-the-ui.yml new file mode 100644 index 00000000000..5925da14f89 --- /dev/null +++ b/changelogs/unreleased/35052-please-select-a-file-when-attempting-to-upload-or-replace-from-the-ui.yml @@ -0,0 +1,4 @@ +--- +title: improve file upload/replace experience +merge_request: +author: diff --git a/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml b/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml new file mode 100644 index 00000000000..3cdb3011f5b --- /dev/null +++ b/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml @@ -0,0 +1,4 @@ +--- +title: Raise guessed encoding confidence threshold to 50 +merge_request: 12990 +author: diff --git a/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml b/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml new file mode 100644 index 00000000000..ea8f31cca9d --- /dev/null +++ b/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml @@ -0,0 +1,4 @@ +--- +title: Fix bar chart does not display label at 0 hour +merge_request: 35136 +author: Jason Dai diff --git a/changelogs/unreleased/35232-next-unresolved.yml b/changelogs/unreleased/35232-next-unresolved.yml new file mode 100644 index 00000000000..45f3fb429a8 --- /dev/null +++ b/changelogs/unreleased/35232-next-unresolved.yml @@ -0,0 +1,4 @@ +--- +title: fix jump to next discussion button +merge_request: +author: diff --git a/changelogs/unreleased/35408-group-auto-avatars.yml b/changelogs/unreleased/35408-group-auto-avatars.yml new file mode 100644 index 00000000000..77b644a7f94 --- /dev/null +++ b/changelogs/unreleased/35408-group-auto-avatars.yml @@ -0,0 +1,4 @@ +--- +title: Show auto-generated avatars for Groups without avatars +merge_request: 13188 +author: diff --git a/changelogs/unreleased/35483-improve-mobile-sidebar.yml b/changelogs/unreleased/35483-improve-mobile-sidebar.yml new file mode 100644 index 00000000000..eb3dab1da9e --- /dev/null +++ b/changelogs/unreleased/35483-improve-mobile-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Improve mobile sidebar +merge_request: +author: diff --git a/changelogs/unreleased/35659-rename-pipeline.yml b/changelogs/unreleased/35659-rename-pipeline.yml new file mode 100644 index 00000000000..0fe211868e4 --- /dev/null +++ b/changelogs/unreleased/35659-rename-pipeline.yml @@ -0,0 +1,4 @@ +--- +title: Rename Pipelines tab to CI / CD in new navigation +merge_request: +author: diff --git a/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml b/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml new file mode 100644 index 00000000000..54b2e71bef9 --- /dev/null +++ b/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml @@ -0,0 +1,4 @@ +--- +title: Allow any logged in users to read_users_list even if it's restricted +merge_request: 13201 +author: diff --git a/changelogs/unreleased/35761-convdev-perc.yml b/changelogs/unreleased/35761-convdev-perc.yml new file mode 100644 index 00000000000..319c4d18219 --- /dev/null +++ b/changelogs/unreleased/35761-convdev-perc.yml @@ -0,0 +1,4 @@ +--- +title: Store & use ConvDev percentages returned by the Version app +merge_request: +author: diff --git a/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml b/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml new file mode 100644 index 00000000000..ac480993d85 --- /dev/null +++ b/changelogs/unreleased/35769-fix-ruby-2-4-compatibility.yml @@ -0,0 +1,4 @@ +--- +title: Fix Issue board when using Ruby 2.4 +merge_request: 13220 +author: diff --git a/changelogs/unreleased/35815-webhook-log-encoding-error.yml b/changelogs/unreleased/35815-webhook-log-encoding-error.yml new file mode 100644 index 00000000000..76ec235086c --- /dev/null +++ b/changelogs/unreleased/35815-webhook-log-encoding-error.yml @@ -0,0 +1,4 @@ +--- +title: Fix encoding error for WebHook logging +merge_request: 13230 +author: Alexander Randa (@randaalex) diff --git a/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml b/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml new file mode 100644 index 00000000000..04791e09b84 --- /dev/null +++ b/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml @@ -0,0 +1,4 @@ +--- +title: Add checks for branch existence before changing HEAD +merge_request: 13359 +author: Vitaliy @blackst0ne Klachkov diff --git a/changelogs/unreleased/36119-issuable-workers.yml b/changelogs/unreleased/36119-issuable-workers.yml new file mode 100644 index 00000000000..beb01ae5b1a --- /dev/null +++ b/changelogs/unreleased/36119-issuable-workers.yml @@ -0,0 +1,4 @@ +--- +title: Simplify checking if objects exist code in new issaubles workers +merge_request: +author: diff --git a/changelogs/unreleased/3686_make_tarball_download_url.yml b/changelogs/unreleased/3686_make_tarball_download_url.yml new file mode 100644 index 00000000000..4e75e52e3ac --- /dev/null +++ b/changelogs/unreleased/3686_make_tarball_download_url.yml @@ -0,0 +1,4 @@ +--- +title: repository archive download url now ends with selected file extension +merge_request: 13178 +author: haseebeqx diff --git a/changelogs/unreleased/add-filtered-search-group-issues-ce.yml b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml new file mode 100644 index 00000000000..f83f4173890 --- /dev/null +++ b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml @@ -0,0 +1,4 @@ +--- +title: Add filtered search to group issue dashboard +merge_request: +author: diff --git a/changelogs/unreleased/add-star-for-action-scope.yml b/changelogs/unreleased/add-star-for-action-scope.yml new file mode 100644 index 00000000000..a8119a01ec4 --- /dev/null +++ b/changelogs/unreleased/add-star-for-action-scope.yml @@ -0,0 +1,4 @@ +--- +title: Add star for action scope, in order to delete image from registry +merge_request: 13248 +author: jean diff --git a/changelogs/unreleased/bvl-nfs-circuitbreaker.yml b/changelogs/unreleased/bvl-nfs-circuitbreaker.yml new file mode 100644 index 00000000000..151854ed31f --- /dev/null +++ b/changelogs/unreleased/bvl-nfs-circuitbreaker.yml @@ -0,0 +1,4 @@ +--- +title: Block access to failing repository storage +merge_request: 11449 +author: diff --git a/changelogs/unreleased/diff-changed-files-dropdown.yml b/changelogs/unreleased/diff-changed-files-dropdown.yml new file mode 100644 index 00000000000..2d2a26ffea2 --- /dev/null +++ b/changelogs/unreleased/diff-changed-files-dropdown.yml @@ -0,0 +1,4 @@ +--- +title: Moved diff changed files into a dropdown +merge_request: +author: diff --git a/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml b/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml new file mode 100644 index 00000000000..8ecea635ce5 --- /dev/null +++ b/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml @@ -0,0 +1,4 @@ +--- +title: "Improve performance of checking for projects on the projects dashboard" +merge_request: +author: diff --git a/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml b/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml new file mode 100644 index 00000000000..e550e0b2f44 --- /dev/null +++ b/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml @@ -0,0 +1,4 @@ +--- +title: Eager load project creators for project dashboards +merge_request: +author: diff --git a/changelogs/unreleased/ericy_ts-protected_branches_api.yml b/changelogs/unreleased/ericy_ts-protected_branches_api.yml new file mode 100644 index 00000000000..4cd275c5e8f --- /dev/null +++ b/changelogs/unreleased/ericy_ts-protected_branches_api.yml @@ -0,0 +1,5 @@ +--- +title: Add API for protected branches to allow for wildcard matching and no access + restrictions +merge_request: 12756 +author: Eric Yu diff --git a/changelogs/unreleased/fix-oauth-checkboxes.yml b/changelogs/unreleased/fix-oauth-checkboxes.yml new file mode 100644 index 00000000000..2839ccc42cb --- /dev/null +++ b/changelogs/unreleased/fix-oauth-checkboxes.yml @@ -0,0 +1,4 @@ +--- +title: Fixed sign-in restrictions buttons not toggling active state +merge_request: +author: diff --git a/changelogs/unreleased/fix-sm-34547-cannot-connect-to-ci-server-error-messages.yml b/changelogs/unreleased/fix-sm-34547-cannot-connect-to-ci-server-error-messages.yml new file mode 100644 index 00000000000..ddaec4f19f9 --- /dev/null +++ b/changelogs/unreleased/fix-sm-34547-cannot-connect-to-ci-server-error-messages.yml @@ -0,0 +1,5 @@ +--- +title: Fix an order of operations for CI connection error message in merge request + widget +merge_request: 13252 +author: diff --git a/changelogs/unreleased/fix-sm-35931-active-ci-pipelineschedule-have-nullified-next_run_at.yml b/changelogs/unreleased/fix-sm-35931-active-ci-pipelineschedule-have-nullified-next_run_at.yml new file mode 100644 index 00000000000..07840205b6e --- /dev/null +++ b/changelogs/unreleased/fix-sm-35931-active-ci-pipelineschedule-have-nullified-next_run_at.yml @@ -0,0 +1,4 @@ +--- +title: Fix pipeline_schedules pages when active schedule has an abnormal state +merge_request: 13286 +author: diff --git a/changelogs/unreleased/github.yml b/changelogs/unreleased/github.yml new file mode 100644 index 00000000000..585b9b13b65 --- /dev/null +++ b/changelogs/unreleased/github.yml @@ -0,0 +1,4 @@ +--- +title: Reduce memory usage of the GitHub importer +merge_request: 12886 +author: diff --git a/changelogs/unreleased/group-milestone-references-system-notes.yml b/changelogs/unreleased/group-milestone-references-system-notes.yml new file mode 100644 index 00000000000..58215352305 --- /dev/null +++ b/changelogs/unreleased/group-milestone-references-system-notes.yml @@ -0,0 +1,4 @@ +--- +title: Support Markdown references, autocomplete, and quick actions for group milestones +merge_request: +author: diff --git a/changelogs/unreleased/group-new-issue.yml b/changelogs/unreleased/group-new-issue.yml new file mode 100644 index 00000000000..5480a44526b --- /dev/null +++ b/changelogs/unreleased/group-new-issue.yml @@ -0,0 +1,4 @@ +--- +title: Cache recent projects for group-level new resource creation. +merge_request: !13058 +author: diff --git a/changelogs/unreleased/handle-reserved-words-for-oauth-usernames.yml b/changelogs/unreleased/handle-reserved-words-for-oauth-usernames.yml new file mode 100644 index 00000000000..0d64844a2b8 --- /dev/null +++ b/changelogs/unreleased/handle-reserved-words-for-oauth-usernames.yml @@ -0,0 +1,4 @@ +--- +title: Uniquify reserved word usernames on OAuth user creation +merge_request: 13244 +author: Robin Bobbitt diff --git a/changelogs/unreleased/mattermost_fixes.yml b/changelogs/unreleased/mattermost_fixes.yml new file mode 100644 index 00000000000..667109a0bb4 --- /dev/null +++ b/changelogs/unreleased/mattermost_fixes.yml @@ -0,0 +1,4 @@ +--- +title: Fix Mattermost integration +merge_request: +author: diff --git a/changelogs/unreleased/memoize-user-personal-projects-count.yml b/changelogs/unreleased/memoize-user-personal-projects-count.yml new file mode 100644 index 00000000000..3839a97f185 --- /dev/null +++ b/changelogs/unreleased/memoize-user-personal-projects-count.yml @@ -0,0 +1,4 @@ +--- +title: Memoize the number of personal projects a user has to reduce COUNT queries +merge_request: +author: diff --git a/changelogs/unreleased/mk-fix-deploy-key-deletion.yml b/changelogs/unreleased/mk-fix-deploy-key-deletion.yml new file mode 100644 index 00000000000..9ff2e49b14c --- /dev/null +++ b/changelogs/unreleased/mk-fix-deploy-key-deletion.yml @@ -0,0 +1,4 @@ +--- +title: Fix deletion of deploy keys linked to other projects +merge_request: 13162 +author: diff --git a/changelogs/unreleased/mk-validate-username-change-with-container-registry-tags.yml b/changelogs/unreleased/mk-validate-username-change-with-container-registry-tags.yml new file mode 100644 index 00000000000..425d5231e14 --- /dev/null +++ b/changelogs/unreleased/mk-validate-username-change-with-container-registry-tags.yml @@ -0,0 +1,4 @@ +--- +title: Add missing validation error for username change with container registry tags +merge_request: 13356 +author: diff --git a/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml new file mode 100644 index 00000000000..71eabdc16d2 --- /dev/null +++ b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml @@ -0,0 +1,4 @@ +--- +title: Add Prometheus metrics exporter to Sidekiq +merge_request: 13082 +author: diff --git a/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml b/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml new file mode 100644 index 00000000000..c1e831306df --- /dev/null +++ b/changelogs/unreleased/pawel-add_more_variables_to_additional_metrics-35267.yml @@ -0,0 +1,4 @@ +--- +title: Add support for kube_namespace in Metrics queries +merge_request: 16169 +author: diff --git a/changelogs/unreleased/project-foreign-keys-without-errors.yml b/changelogs/unreleased/project-foreign-keys-without-errors.yml new file mode 100644 index 00000000000..63c53c8ad8f --- /dev/null +++ b/changelogs/unreleased/project-foreign-keys-without-errors.yml @@ -0,0 +1,4 @@ +--- +title: Change project FK migration to skip existing FKs +merge_request: +author: diff --git a/changelogs/unreleased/rc-fix-branches-api-endpoint.yml b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml index a8f49298258..b36663bbe91 100644 --- a/changelogs/unreleased/rc-fix-branches-api-endpoint.yml +++ b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml @@ -1,5 +1,5 @@ --- title: Fix the /projects/:id/repository/branches endpoint to handle dots in the branch - name when the project full patch contains a `/` + name when the project full path contains a `/` merge_request: 13115 author: diff --git a/changelogs/unreleased/rc-fix-commits-api.yml b/changelogs/unreleased/rc-fix-commits-api.yml new file mode 100644 index 00000000000..215429eaf6b --- /dev/null +++ b/changelogs/unreleased/rc-fix-commits-api.yml @@ -0,0 +1,5 @@ +--- +title: Fix the /projects/:id/repository/commits endpoint to handle dots in the ref + name when the project full path contains a `/` +merge_request: 13370 +author: diff --git a/changelogs/unreleased/rc-fix-tags-api.yml b/changelogs/unreleased/rc-fix-tags-api.yml new file mode 100644 index 00000000000..0a7dd5ca6ab --- /dev/null +++ b/changelogs/unreleased/rc-fix-tags-api.yml @@ -0,0 +1,5 @@ +--- +title: Fix the /projects/:id/repository/tags endpoint to handle dots in the tag name + when the project full path contains a `/` +merge_request: 13368 +author: diff --git a/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml b/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml new file mode 100644 index 00000000000..83934217e6a --- /dev/null +++ b/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml @@ -0,0 +1,4 @@ +--- +title: Remove redundant query when retrieving the most recent push of a user +merge_request: +author: diff --git a/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml b/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml new file mode 100644 index 00000000000..5bfe55e562f --- /dev/null +++ b/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml @@ -0,0 +1,4 @@ +--- +title: Re-organise "issues" indexes for faster ordering +merge_request: +author: diff --git a/changelogs/unreleased/restrict-haml-javascript.yml b/changelogs/unreleased/restrict-haml-javascript.yml new file mode 100644 index 00000000000..3d0a52f416d --- /dev/null +++ b/changelogs/unreleased/restrict-haml-javascript.yml @@ -0,0 +1,4 @@ +--- +title: Add custom linter for inline JavaScript to haml_lint +merge_request: 9742 +author: winniehell diff --git a/changelogs/unreleased/search-flickering.yml b/changelogs/unreleased/search-flickering.yml new file mode 100644 index 00000000000..951a5a0292a --- /dev/null +++ b/changelogs/unreleased/search-flickering.yml @@ -0,0 +1,4 @@ +--- +title: Fix search box losing focus when typing +merge_request: +author: diff --git a/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml b/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml new file mode 100644 index 00000000000..9ca5f81cf79 --- /dev/null +++ b/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml @@ -0,0 +1,4 @@ +--- +title: Make Delete Merged Branches handle wildcard protected branches correctly +merge_request: 13251 +author: diff --git a/changelogs/unreleased/tc-no-todo-service-select.yml b/changelogs/unreleased/tc-no-todo-service-select.yml new file mode 100644 index 00000000000..ddcae334aa7 --- /dev/null +++ b/changelogs/unreleased/tc-no-todo-service-select.yml @@ -0,0 +1,4 @@ +--- +title: Avoid plucking Todo ids in TodoService +merge_request: 10845 +author: diff --git a/changelogs/unreleased/wiki_title.yml b/changelogs/unreleased/wiki_title.yml new file mode 100644 index 00000000000..3ef5fa2969b --- /dev/null +++ b/changelogs/unreleased/wiki_title.yml @@ -0,0 +1,4 @@ +--- +title: Allow wiki pages to be renamed in the UI +merge_request: 10069 +author: wendy0402 diff --git a/changelogs/unreleased/winh-derive-project-name.yml b/changelogs/unreleased/winh-derive-project-name.yml new file mode 100644 index 00000000000..2244d21d768 --- /dev/null +++ b/changelogs/unreleased/winh-derive-project-name.yml @@ -0,0 +1,4 @@ +--- +title: Derive project path from import URL +merge_request: 13131 +author: diff --git a/changelogs/unreleased/zj-project-templates.yml b/changelogs/unreleased/zj-project-templates.yml new file mode 100644 index 00000000000..ab6e0f2d5f2 --- /dev/null +++ b/changelogs/unreleased/zj-project-templates.yml @@ -0,0 +1,4 @@ +--- +title: Projects can be created from templates +merge_request: 13108 +author: diff --git a/config/application.rb b/config/application.rb index 1c13cc81270..f69dab4de39 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,13 +23,13 @@ module Gitlab # https://github.com/rails/rails/blob/v4.2.6/railties/lib/rails/engine.rb#L687 # This is a nice reference article on autoloading/eager loading: # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload - config.eager_load_paths.push(*%W(#{config.root}/lib + config.eager_load_paths.push(*%W[#{config.root}/lib #{config.root}/app/models/hooks #{config.root}/app/models/members #{config.root}/app/models/project_services #{config.root}/app/workers/concerns #{config.root}/app/services/concerns - #{config.root}/app/finders/concerns)) + #{config.root}/app/finders/concerns]) config.generators.templates.push("#{config.root}/generator_templates") @@ -176,12 +176,16 @@ module Gitlab next unless name.include?('namespace_project') define_method(name.sub('namespace_project', 'project')) do |project, *args| - send(name, project&.namespace, project, *args) + send(name, project&.namespace, project, *args) # rubocop:disable GitlabSecurity/PublicSend end end end + # We add the MilestonesRoutingHelper because we know that this does not + # conflict with the methods defined in `project_url_helpers`, and we want + # these methods available in the same places. Gitlab::Routing.add_helpers(project_url_helpers) + Gitlab::Routing.add_helpers(MilestonesRoutingHelper) end end end diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 59c7050a14d..ca5b941aebf 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -398,3 +398,9 @@ :why: https://github.com/remy/undefsafe/blob/master/LICENSE :versions: [] :when: 2017-04-10 06:30:00.002555000 Z +- - :approve + - thunky + - :who: Mike Greiling + :why: https://github.com/mafintosh/thunky/blob/master/README.md#license + :versions: [] + :when: 2017-08-07 05:56:09.907045000 Z diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e9bf2df490f..e73db08fcac 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -282,7 +282,7 @@ production: &base # # Example: '/etc/ca.pem' # - ca_cert: '' + ca_file: '' # Specifies the SSL version for OpenSSL to use, if the OpenSSL default # is not appropriate. @@ -506,6 +506,11 @@ production: &base path: /home/git/repositories/ gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port) # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage. + failure_count_threshold: 10 # number of failures before stopping attempts + failure_wait_time: 30 # Seconds after an access failure before allowing access again + failure_reset_time: 1800 # Time in seconds to expire failures + storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt + ## Backup settings backup: @@ -585,6 +590,12 @@ production: &base ip_whitelist: - 127.0.0.0/8 + # Sidekiq exporter is webserver built in to Sidekiq to expose Prometheus metrics + sidekiq_exporter: + # enabled: true + # address: localhost + # port: 3807 + # # 5. Extra customization # ========================== @@ -638,6 +649,10 @@ test: default: path: tmp/tests/repositories/ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket + broken: + path: tmp/tests/non-existent-repositories + gitaly_address: unix:tmp/tests/gitaly/gitaly.socket + gitaly: enabled: true token: secret diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 63f4c8c9e0a..5c6578d3531 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -71,7 +71,7 @@ class Settings < Settingslogic # check that `current` (string or integer) is a contant in `modul`. def verify_constant(modul, current, default) - constant = modul.constants.find{ |name| modul.const_get(name) == current } + constant = modul.constants.find { |name| modul.const_get(name) == current } value = constant.nil? ? default : modul.const_get(constant) if current.is_a? String value = modul.const_get(current.upcase) rescue default @@ -395,6 +395,10 @@ Settings.cron_jobs['remove_old_web_hook_logs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['remove_old_web_hook_logs_worker']['cron'] ||= '40 0 * * *' Settings.cron_jobs['remove_old_web_hook_logs_worker']['job_class'] = 'RemoveOldWebHookLogsWorker' +Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *' +Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker' + # # GitLab Shell # @@ -433,6 +437,17 @@ end Settings.repositories.storages.values.each do |storage| # Expand relative paths storage['path'] = Settings.absolute(storage['path']) + # Set failure defaults + storage['failure_count_threshold'] ||= 10 + storage['failure_wait_time'] ||= 30 + storage['failure_reset_time'] ||= 1800 + storage['storage_timeout'] ||= 5 + # Set turn strings into numbers + storage['failure_count_threshold'] = storage['failure_count_threshold'].to_i + storage['failure_wait_time'] = storage['failure_wait_time'].to_i + storage['failure_reset_time'] = storage['failure_reset_time'].to_i + # We might want to have a timeout shorter than 1 second. + storage['storage_timeout'] = storage['storage_timeout'].to_f end # @@ -513,6 +528,10 @@ Settings.webpack.dev_server['port'] ||= 3808 Settings['monitoring'] ||= Settingslogic.new({}) Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8'] Settings.monitoring['unicorn_sampler_interval'] ||= 10 +Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({}) +Settings.monitoring.sidekiq_exporter['enabled'] ||= false +Settings.monitoring.sidekiq_exporter['address'] ||= 'localhost' +Settings.monitoring.sidekiq_exporter['port'] ||= 3807 # # Testing settings diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb index 9e24f42d284..92ce4dd03cd 100644 --- a/config/initializers/6_validations.rb +++ b/config/initializers/6_validations.rb @@ -7,6 +7,13 @@ def find_parent_path(name, path) Gitlab.config.repositories.storages.detect do |n, rs| name != n && Pathname.new(rs['path']).realpath == parent end +rescue Errno::EIO, Errno::ENOENT => e + warning = "WARNING: couldn't verify #{path} (#{name}). "\ + "If this is an external storage, it might be offline." + message = "#{warning}\n#{e.message}" + Rails.logger.error("#{message}\n\t" + e.backtrace.join("\n\t")) + + nil end def storage_validation_error(message) @@ -29,6 +36,15 @@ def validate_storages_config if !repository_storage.is_a?(Hash) || repository_storage['path'].nil? storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example") end + + %w(failure_count_threshold failure_wait_time failure_reset_time storage_timeout).each do |setting| + # Falling back to the defaults is fine! + next if repository_storage[setting].nil? + + unless repository_storage[setting].to_f > 0 + storage_validation_error("#{setting}, for storage `#{name}` needs to be greater than 0") + end + end end end diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index a2f8421f5d7..54c797e0714 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -10,3 +10,9 @@ Prometheus::Client.configure do |config| config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir') end end + +Sidekiq.configure_server do |config| + config.on(:startup) do + Gitlab::Metrics::SidekiqMetricsExporter.instance.start + end +end diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb index 9266ff0f615..150aaa2a8c2 100644 --- a/config/initializers/active_record_locking.rb +++ b/config/initializers/active_record_locking.rb @@ -18,7 +18,7 @@ module ActiveRecord lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i + previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend # This line is added as a patch previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 @@ -48,7 +48,7 @@ module ActiveRecord # If something went wrong, revert the version. rescue Exception - send(lock_col + '=', previous_lock_value) + send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend raise end end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 5427bab93ce..c0748231813 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -67,7 +67,9 @@ namespace :admin do end resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] + resource :health_check, controller: 'health_check', only: [:show] do + post :reset_storage_health + end resource :background_jobs, controller: 'background_jobs', only: [:show] resource :system_info, controller: 'system_info', only: [:show] resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } diff --git a/config/routes/repository.rb b/config/routes/repository.rb index edcf3ddf57b..2ba16035ece 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -2,7 +2,7 @@ resource :repository, only: [:create] do member do - get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex } + get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive' end end diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 7496bfa4fbb..83abc83c9f0 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -23,6 +23,8 @@ - [update_merge_requests, 3] - [process_commit, 3] - [new_note, 2] + - [new_issue, 2] + - [new_merge_request, 2] - [build, 2] - [pipeline, 2] - [gitlab_shell, 2] diff --git a/config/webpack.config.js b/config/webpack.config.js index 2f85b89d523..8e1b80cd39f 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -3,7 +3,8 @@ var fs = require('fs'); var path = require('path'); var webpack = require('webpack'); -var StatsPlugin = require('stats-webpack-plugin'); +var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; +var CopyWebpackPlugin = require('copy-webpack-plugin'); var CompressionPlugin = require('compression-webpack-plugin'); var NameAllModulesPlugin = require('name-all-modules-plugin'); var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; @@ -39,6 +40,8 @@ var config = { environments_folder: './environments/folder/environments_folder_bundle.js', filtered_search: './filtered_search/filtered_search_bundle.js', graphs: './graphs/graphs_bundle.js', + graphs_charts: './graphs/graphs_charts.js', + graphs_show: './graphs/graphs_show.js', group: './group.js', groups: './groups/index.js', groups_list: './groups_list.js', @@ -58,10 +61,12 @@ var config = { pipelines_details: './pipelines/pipeline_details_bundle.js', pipelines_times: './pipelines/pipelines_times.js', profile: './profile/profile_bundle.js', + project_import_gl: './projects/project_import_gitlab_project.js', project_new: './projects/project_new.js', prometheus_metrics: './prometheus_metrics', protected_branches: './protected_branches', protected_tags: './protected_tags', + repo: './repo/index.js', sidebar: './sidebar/sidebar_bundle.js', schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js', schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js', @@ -70,9 +75,12 @@ var config = { stl_viewer: './blob/stl_viewer.js', terminal: './terminal/terminal_bundle.js', u2f: ['vendor/u2f'], + ui_development_kit: './ui_development_kit.js', + users: './users/index.js', raven: './raven/index.js', vue_merge_request_widget: './vue_merge_request_widget/index.js', test: './test.js', + two_factor_auth: './two_factor_auth.js', performance_bar: './performance_bar.js', webpack_runtime: './webpack.js', }, @@ -105,26 +113,44 @@ var config = { options: { limit: 2048 }, }, { - test: /\.(worker\.js|pdf|bmpr)$/, + test: /\.(worker(\.min)?\.js|pdf|bmpr)$/, exclude: /node_modules/, loader: 'file-loader', + options: { + name: '[name].[hash].[ext]', + } }, { test: /locale\/\w+\/(.*)\.js$/, loader: 'exports-loader?locales', }, - ] + { + test: /monaco-editor\/\w+\/vs\/loader\.js$/, + use: [ + { loader: 'exports-loader', options: 'l.global' }, + { loader: 'imports-loader', options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined' }, + ], + } + ], + + noParse: [/monaco-editor\/\w+\/vs\//], }, plugins: [ // manifest filename must match config.webpack.manifest_filename // webpack-rails only needs assetsByChunkName to function properly - new StatsPlugin('manifest.json', { - chunkModules: false, - source: false, - chunks: false, - modules: false, - assets: true + new StatsWriterPlugin({ + filename: 'manifest.json', + transform: function(data, opts) { + var stats = opts.compiler.getStats().toJson({ + chunkModules: false, + source: false, + chunks: false, + modules: false, + assets: true + }); + return JSON.stringify(stats, null, 2); + } }), // prevent pikaday from including moment.js @@ -172,6 +198,7 @@ var config = { 'pdf_viewer', 'pipelines', 'pipelines_details', + 'repo', 'schedule_form', 'schedules_index', 'sidebar', @@ -195,6 +222,26 @@ var config = { new webpack.optimize.CommonsChunkPlugin({ names: ['main', 'locale', 'common', 'webpack_runtime'], }), + + // copy pre-compiled vendor libraries verbatim + new CopyWebpackPlugin([ + { + from: path.join(ROOT_PATH, `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`), + to: 'monaco-editor/vs', + transform: function(content, path) { + if (/\.js$/.test(path) && !/worker/i.test(path)) { + return ( + '(function(){\n' + + 'var define = this.define, require = this.require;\n' + + 'window.define = define; window.require = require;\n' + + content + + '\n}.call(window.__monaco_context__ || (window.__monaco_context__ = {})));' + ); + } + return content; + } + } + ]), ], resolve: { @@ -243,6 +290,7 @@ if (IS_DEV_SERVER) { config.devServer = { host: DEV_SERVER_HOST, port: DEV_SERVER_PORT, + disableHostCheck: true, headers: { 'Access-Control-Allow-Origin': '*' }, stats: 'errors-only', hot: DEV_SERVER_LIVERELOAD, diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb index b8f6a803a0a..fcdd79d3b02 100644 --- a/db/migrate/20161017125927_add_unique_index_to_labels.rb +++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb @@ -10,7 +10,7 @@ class AddUniqueIndexToLabels < ActiveRecord::Migration def up select_all('SELECT title, project_id, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label| label_title = quote_string(label['title']) - duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] } + duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map { |label| label['id'] } label_id = duplicated_ids.first duplicated_ids.delete(label_id) diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb index c0cb9d78748..bcdae272209 100644 --- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb +++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb @@ -16,6 +16,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration end def repository_path + # TODO: review if the change from Legacy storage needs to reflect here as well. File.join(repository_storage_path, read_attribute(:path_with_namespace) + '.git') end diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb index 3eaafac321d..af6d10b5158 100644 --- a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb +++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb @@ -62,8 +62,8 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration # These columns are not indexed yet, meaning a cascading delete would take # forever. - add_concurrent_index(:project_group_links, :project_id) - add_concurrent_index(:pages_domains, :project_id) + add_index_if_not_exists(:project_group_links, :project_id) + add_index_if_not_exists(:pages_domains, :project_id) end def down @@ -71,15 +71,15 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration remove_foreign_key_without_error(source, column) end - add_concurrent_foreign_key(:boards, :projects, column: :project_id) - add_concurrent_foreign_key(:lists, :labels, column: :label_id) - add_concurrent_foreign_key(:lists, :boards, column: :board_id) + add_foreign_key_if_not_exists(:boards, :projects, column: :project_id) + add_foreign_key_if_not_exists(:lists, :labels, column: :label_id) + add_foreign_key_if_not_exists(:lists, :boards, column: :board_id) - add_concurrent_foreign_key(:protected_branch_merge_access_levels, + add_foreign_key_if_not_exists(:protected_branch_merge_access_levels, :protected_branches, column: :protected_branch_id) - add_concurrent_foreign_key(:protected_branch_push_access_levels, + add_foreign_key_if_not_exists(:protected_branch_push_access_levels, :protected_branches, column: :protected_branch_id) @@ -89,7 +89,7 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration def add_foreign_keys TABLES.each do |(source, target, column)| - add_concurrent_foreign_key(source, target, column: column) + add_foreign_key_if_not_exists(source, target, column: column) end end @@ -153,6 +153,18 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration EOF end + def add_foreign_key_if_not_exists(source, target, column:) + return if foreign_key_exists?(source, column) + + add_concurrent_foreign_key(source, target, column: column) + end + + def add_index_if_not_exists(table, column) + return if index_exists?(table, column) + + add_concurrent_index(table, column) + end + def remove_foreign_key_without_error(table, column) remove_foreign_key(table, column: column) rescue ArgumentError @@ -163,6 +175,12 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration rescue ArgumentError end + def foreign_key_exists?(table, column) + foreign_keys(table).any? do |key| + key.options[:column] == column.to_s + end + end + def connection # Rails memoizes connection objects, but this causes them to be shared # amongst threads; we don't want that. diff --git a/db/migrate/20170731175128_add_percentages_to_conv_dev.rb b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb new file mode 100644 index 00000000000..1819bfc96bb --- /dev/null +++ b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb @@ -0,0 +1,32 @@ +class AddPercentagesToConvDev < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default :conversational_development_index_metrics, :percentage_boards, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_ci_pipelines, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_deployments, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_environments, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_issues, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_merge_requests, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_milestones, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_notes, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_projects_prometheus_active, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_service_desk_issues, :float, allow_null: false, default: 0 + end + + def down + remove_column :conversational_development_index_metrics, :percentage_boards + remove_column :conversational_development_index_metrics, :percentage_ci_pipelines + remove_column :conversational_development_index_metrics, :percentage_deployments + remove_column :conversational_development_index_metrics, :percentage_environments + remove_column :conversational_development_index_metrics, :percentage_issues + remove_column :conversational_development_index_metrics, :percentage_merge_requests + remove_column :conversational_development_index_metrics, :percentage_milestones + remove_column :conversational_development_index_metrics, :percentage_notes + remove_column :conversational_development_index_metrics, :percentage_projects_prometheus_active + remove_column :conversational_development_index_metrics, :percentage_service_desk_issues + end +end diff --git a/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb new file mode 100644 index 00000000000..a7d8f2f3604 --- /dev/null +++ b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb @@ -0,0 +1,7 @@ +class AddMergeJidToMergeRequests < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :merge_requests, :merge_jid, :string + end +end diff --git a/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb b/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb new file mode 100644 index 00000000000..eb7d1be1732 --- /dev/null +++ b/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb @@ -0,0 +1,43 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ReorganiseIssuesIndexesForFasterSorting < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + REMOVE_INDEX_COLUMNS = %i[project_id created_at due_date updated_at].freeze + + ADD_INDEX_COLUMNS = [ + %i[project_id created_at id state], + %i[project_id due_date id state], + %i[project_id updated_at id state] + ].freeze + + TABLE = :issues + + def up + add_indexes(ADD_INDEX_COLUMNS) + remove_indexes(REMOVE_INDEX_COLUMNS) + end + + def down + add_indexes(REMOVE_INDEX_COLUMNS) + remove_indexes(ADD_INDEX_COLUMNS) + end + + def add_indexes(columns) + columns.each do |column| + add_concurrent_index(TABLE, column) unless index_exists?(TABLE, column) + end + end + + def remove_indexes(columns) + columns.each do |column| + remove_concurrent_index(TABLE, column) if index_exists?(TABLE, column) + end + end +end diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb index c1e64f20109..5238a2ba1b7 100644 --- a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb +++ b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb @@ -30,7 +30,7 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration private def pending_delete_batch - connection.exec_query(find_batch).map{ |row| row['id'].to_i } + connection.exec_query(find_batch).map { |row| row['id'].to_i } end BATCH_SIZE = 5000 diff --git a/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb b/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb new file mode 100644 index 00000000000..17a9dc293f1 --- /dev/null +++ b/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb @@ -0,0 +1,33 @@ +class ScheduleMergeRequestDiffMigrations < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 2500 + MIGRATION = 'DeserializeMergeRequestDiffsAndCommits' + + disable_ddl_transaction! + + class MergeRequestDiff < ActiveRecord::Base + self.table_name = 'merge_request_diffs' + + include ::EachBatch + end + + # Assuming that there are 5 million rows affected (which is more than on + # GitLab.com), and that each batch of 2,500 rows takes up to 5 minutes, then + # we can migrate all the rows in 7 days. + # + # On staging, plucking the IDs themselves takes 5 seconds. + def up + non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' + + MergeRequestDiff.where(non_empty).each_batch(of: BATCH_SIZE) do |relation, index| + range = relation.pluck('MIN(id)', 'MAX(id)').first + + BackgroundMigrationWorker.perform_in(index * 5.minutes, MIGRATION, range) + end + end + + def down + end +end diff --git a/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb b/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb new file mode 100644 index 00000000000..cd533391d8d --- /dev/null +++ b/db/post_migrate/20170728101014_remove_events_from_notification_settings.rb @@ -0,0 +1,9 @@ +class RemoveEventsFromNotificationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + remove_column :notification_settings, :events, :text + end +end diff --git a/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb new file mode 100644 index 00000000000..9af76c94bf3 --- /dev/null +++ b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb @@ -0,0 +1,30 @@ +class CalculateConvDevIndexPercentages < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + DOWNTIME = false + + class ConversationalDevelopmentIndexMetric < ActiveRecord::Base + self.table_name = 'conversational_development_index_metrics' + + METRICS = %w[boards ci_pipelines deployments environments issues merge_requests milestones notes + projects_prometheus_active service_desk_issues] + end + + def up + ConversationalDevelopmentIndexMetric.find_each do |conv_dev_index| + update = [] + + ConversationalDevelopmentIndexMetric::METRICS.each do |metric| + instance_score = conv_dev_index["instance_#{metric}"].to_f + leader_score = conv_dev_index["leader_#{metric}"].to_f + + percentage = leader_score.zero? ? 0.0 : (instance_score / leader_score) * 100 + update << "percentage_#{metric} = '#{percentage}'" + end + + execute("UPDATE conversational_development_index_metrics SET #{update.join(',')} WHERE id = #{conv_dev_index.id}") + end + end + + def down + end +end diff --git a/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb new file mode 100644 index 00000000000..ea3d1fb3e02 --- /dev/null +++ b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb @@ -0,0 +1,11 @@ +class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration + DOWNTIME = false + + def up + remove_column :merge_requests, :locked_at + end + + def down + add_column :merge_requests, :locked_at, :datetime_with_timezone + end +end diff --git a/db/schema.rb b/db/schema.rb index 5fbbdea6eaa..ed3cf70bcdd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170725145659) do +ActiveRecord::Schema.define(version: 20170807160457) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -451,6 +451,16 @@ ActiveRecord::Schema.define(version: 20170725145659) do t.float "instance_service_desk_issues", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.float "percentage_boards", default: 0.0, null: false + t.float "percentage_ci_pipelines", default: 0.0, null: false + t.float "percentage_deployments", default: 0.0, null: false + t.float "percentage_environments", default: 0.0, null: false + t.float "percentage_issues", default: 0.0, null: false + t.float "percentage_merge_requests", default: 0.0, null: false + t.float "percentage_milestones", default: 0.0, null: false + t.float "percentage_notes", default: 0.0, null: false + t.float "percentage_projects_prometheus_active", default: 0.0, null: false + t.float "percentage_service_desk_issues", default: 0.0, null: false end create_table "deploy_keys_projects", force: :cascade do |t| @@ -641,12 +651,13 @@ ActiveRecord::Schema.define(version: 20170725145659) do add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree - add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} - add_index "issues", ["due_date"], name: "index_issues_on_due_date", using: :btree add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree + add_index "issues", ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state", using: :btree + add_index "issues", ["project_id", "due_date", "id", "state"], name: "index_issues_on_project_id_and_due_date_and_id_and_state", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree + add_index "issues", ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state", using: :btree add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} @@ -839,7 +850,6 @@ ActiveRecord::Schema.define(version: 20170725145659) do t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.datetime "locked_at" t.integer "updated_by_id" t.text "merge_error" t.text "merge_params" @@ -857,6 +867,7 @@ ActiveRecord::Schema.define(version: 20170725145659) do t.integer "last_edited_by_id" t.integer "head_pipeline_id" t.boolean "ref_fetched" + t.string "merge_jid" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -981,7 +992,6 @@ ActiveRecord::Schema.define(version: 20170725145659) do t.integer "level", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "events" t.boolean "new_note" t.boolean "new_issue" t.boolean "reopen_issue" diff --git a/doc/README.md b/doc/README.md index 8bb8e147cd1..ca4790ceda0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,7 +3,7 @@ Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured platform for software development! -We offer four different products for you and your company: +GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans: - **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/), self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com. diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index a7395e03d1c..425c924cdf2 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -96,7 +96,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # # Example: '/etc/ca.pem' # - ca_cert: '' + ca_file: '' # Specifies the SSL version for OpenSSL to use, if the OpenSSL default # is not appropriate. @@ -259,9 +259,9 @@ group you can use the following syntax: (memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com) ``` -Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at +Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for -nested members in the user filter should not be confused with +nested members in the user filter should not be confused with [group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes). Please note that GitLab does not support the custom filter syntax used by diff --git a/doc/administration/img/failing_storage.png b/doc/administration/img/failing_storage.png Binary files differnew file mode 100644 index 00000000000..82b393a58b2 --- /dev/null +++ b/doc/administration/img/failing_storage.png diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 4b8d5c5cc87..76e071dc673 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -9,6 +9,33 @@ documentation](http://docs.gitlab.com/ee/administration/audit_events.html) System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. +## `production_json.log` + +This file lives in `/var/log/gitlab/gitlab-rails/production_json.log` for +Omnibus GitLab packages or in `/home/git/gitlab/log/production_json.log` for +installations from source. (When Gitlab is running in an environment +other than production, the corresponding logfile is shown here.) + +It contains a structured log for Rails controller requests received from +GitLab, thanks to [Lograge](https://github.com/roidrage/lograge/). Note that +requests from the API [are not yet logged to this +file](https://gitlab.com/gitlab-org/gitlab-ce/issues/36189). + +Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example: + +```json +{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":{"namespace_id":"gitlab","project_id":"gitlab-ce","id":"1234"},"remote_ip":"18.245.0.1","user_id":1,"username":"admin"} +``` + +In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data: + +1. `duration`: the total time taken to retrieve the request +2. `view`: total time taken inside the Rails views +3. `db`: total time to retrieve data from the database + +In addition, the log contains the IP address from which the request originated +(`remote_ip`) as well as the user's ID (`user_id`), and username (`username`). + ## `production.log` This file lives in `/var/log/gitlab/gitlab-rails/production.log` for diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md index 3b8c716eff5..a1bb3851951 100644 --- a/doc/administration/reply_by_email_postfix_setup.md +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -177,6 +177,20 @@ Courier, which we will install later to add IMAP authentication, requires mailbo ```sh sudo apt-get install courier-imap ``` + + And start `imapd`: + ```sh + imapd start + ``` + +1. The courier-authdaemon isn't started after installation. Without it, imap authentication will fail: + ```sh + sudo service courier-authdaemon start + ``` + You can also configure courier-authdaemon to start on boot: + ```sh + sudo systemctl enable courier-authdaemon + ``` ## Configure Postfix to receive email from the internet diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md index 55a45119525..624a908b3a3 100644 --- a/doc/administration/repository_storage_paths.md +++ b/doc/administration/repository_storage_paths.md @@ -60,7 +60,7 @@ respectively. path: /mnt/cephfs/repositories ``` -1. [Restart GitLab] for the changes to take effect. +1. [Restart GitLab][restart-gitlab] for the changes to take effect. >**Note:** The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be @@ -97,9 +97,80 @@ be stored via the **Application Settings** in the Admin area. Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be randomly placed on one of the selected paths. +## Handling failing repository storage + +> [Introduced][ce-11449] in GitLab 9.5. + +When GitLab detects access to the repositories storage fails repeatedly, it can +gracefully prevent attempts to access the storage. This might be useful when +the repositories are stored somewhere on the network. + +The configuration could look as follows: + +**For Omnibus installations** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + git_data_dirs({ + "default" => { + "path" => "/mnt/nfs-01/git-data", + "failure_count_threshold" => 10, + "failure_wait_time" => 30, + "failure_reset_time" => 1800, + "storage_timeout" => 5 + } + }) + ``` + +1. Save the file and [reconfigure GitLab][reconfigure-gitlab] for the changes to take effect. + +--- + +**For installations from source** + +1. Edit `config/gitlab.yml`: + + ```yaml + repositories: + storages: # You must have at least a `default` storage path. + default: + path: /home/git/repositories/ + failure_count_threshold: 10 # number of failures before stopping attempts + failure_wait_time: 30 # Seconds after last access failure before trying again + failure_reset_time: 1800 # Time in seconds to expire failures + storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt + ``` + +1. Save the file and [restart GitLab][restart-gitlab] for the changes to take effect. + + +**`failure_count_threshold`:** The number of failures of after which GitLab will +completely prevent access to the storage. The number of failures can be reset in +the admin interface: `https://gitlab.example.com/admin/health_check` or using the +[api](../api/repository_storage_health.md) to allow access to the storage again. + +**`failure_wait_time`:** When access to a storage fails. GitLab will prevent +access to the storage for the time specified here. This allows the filesystem to +recover without. + +**`failure_reset_time`:** The time in seconds GitLab will keep failure +information. When no failures occur during this time, information about the +mount is reset. + +**`storage_timeout`:** The time in seconds GitLab will try to access storage. +After this time a timeout error will be raised. + +When storage failures occur, this will be visible in the admin interface like this: + +![failing storage](img/failing_storage.png) + +To allow access to all storages, click the `Reset git storage health information` button. + [ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 -[restart gitlab]: restart_gitlab.md#installations-from-source -[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure +[restart-gitlab]: restart_gitlab.md#installations-from-source +[reconfigure-gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure [backups]: ../raketasks/backup_restore.md [raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56 [repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457 +[ce-11449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11449 diff --git a/doc/api/README.md b/doc/api/README.md index fe29563eaca..8acb2145f1a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -11,7 +11,8 @@ following locations: - [Award Emoji](award_emoji.md) - [Branches](branches.md) - [Broadcast Messages](broadcast_messages.md) -- [Build Variables](build_variables.md) +- [Project-level Variables](project_level_variables.md) +- [Group-level Variables](group_level_variables.md) - [Commits](commits.md) - [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) @@ -42,6 +43,7 @@ following locations: - [Project Access Requests](access_requests.md) - [Project Members](members.md) - [Project Snippets](project_snippets.md) +- [Protected Branches](protected_branches.md) - [Repositories](repositories.md) - [Repository Files](repository_files.md) - [Runners](runners.md) @@ -75,6 +77,38 @@ controller-specific endpoints. GraphQL has a number of benefits: It will co-exist with the current v4 REST API. If we have a v5 API, this should be a compatibility layer on top of GraphQL. +## Basic usage + +API requests should be prefixed with `api` and the API version. The API version +is defined in [`lib/api.rb`][lib-api-url]. For example, the root of the v4 API +is at `/api/v4`. + +For endpoints that require [authentication](#authentication), you need to pass +a `private_token` parameter via query string or header. If passed as a header, +the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of +an underscore). + +Example of a valid API request: + +``` +GET /projects?private_token=9koXpg98eAheJpvBs5tK +``` + +Example of a valid API request using cURL and authentication via header: + +```shell +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" +``` + +Example of a valid API request using cURL and authentication via a query string: + +```shell +curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK" +``` + +The API uses JSON to serialize data. You don't need to specify `.json` at the +end of an API URL. + ## Authentication Most API requests require authentication via a session cookie or token. For @@ -205,37 +239,6 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" ``` -## Basic usage - -API requests should be prefixed with `api` and the API version. The API version -is defined in [`lib/api.rb`][lib-api-url]. - -For endpoints that require [authentication](#authentication), you need to pass -a `private_token` parameter via query string or header. If passed as a header, -the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of -an underscore). - -Example of a valid API request: - -``` -GET /projects?private_token=9koXpg98eAheJpvBs5tK -``` - -Example of a valid API request using cURL and authentication via header: - -```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" -``` - -Example of a valid API request using cURL and authentication via a query string: - -```shell -curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK" -``` - -The API uses JSON to serialize data. You don't need to specify `.json` at the -end of an API URL. - ## Status codes The API is designed to return different status codes according to context and diff --git a/doc/api/branches.md b/doc/api/branches.md index dfaa7d6fab7..80744258acb 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -95,6 +95,8 @@ Example response: ## Protect repository branch +>**Note:** This API endpoint is deprecated in favor of `POST /projects/:id/protected_branches`. + Protects a single project repository branch. This is an idempotent function, protecting an already protected repository branch still returns a `200 OK` status code. @@ -143,6 +145,8 @@ Example response: ## Unprotect repository branch +>**Note:** This API endpoint is deprecated in favor of `DELETE /projects/:id/protected_branches/:name` + Unprotects a single project repository branch. This is an idempotent function, unprotecting an already unprotected repository branch still returns a `200 OK` status code. diff --git a/doc/api/commits.md b/doc/api/commits.md index c91f9ecbdaf..2a78553782f 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -69,8 +69,9 @@ POST /projects/:id/repository/commits | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | -| `branch` | string | yes | The name of a branch | +| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. | | `commit_message` | string | yes | Commit message | +| `start_branch` | string | no | Name of the branch to start the new commit from | | `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. | | `author_email` | string | no | Specify the commit author's email address | | `author_name` | string | no | Specify the commit author's name | diff --git a/doc/api/events.md b/doc/api/events.md index e7829c9f479..3d5170f3f1e 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -302,6 +302,7 @@ Example response: "project_id":1, "action_name":"opened", "target_id":160, + "target_iid":160, "target_type":"Issue", "author_id":25, "data":null, @@ -322,6 +323,7 @@ Example response: "project_id":1, "action_name":"opened", "target_id":159, + "target_iid":159, "target_type":"Issue", "author_id":21, "data":null, @@ -336,6 +338,45 @@ Example response: "web_url":"https://gitlab.example.com/ted" }, "author_username":"ted" + }, + { + "title": null, + "project_id": 1, + "action_name": "commented on", + "target_id": 1312, + "target_iid": 1312, + "target_type": "Note", + "author_id": 1, + "data": null, + "target_title": null, + "created_at": "2015-12-04T10:33:58.089Z", + "note": { + "id": 1312, + "body": "What an awesome day!", + "attachment": null, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2015-12-04T10:33:56.698Z", + "system": false, + "noteable_id": 377, + "noteable_type": "Issue", + "noteable_iid": 377 + }, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/root" + }, + "author_username": "root" } ] ``` diff --git a/doc/api/group_level_variables.md b/doc/api/group_level_variables.md new file mode 100644 index 00000000000..e19be7b35c4 --- /dev/null +++ b/doc/api/group_level_variables.md @@ -0,0 +1,125 @@ +# Group-level Variables API + +## List group variables + +Get list of a group's variables. + +``` +GET /groups/:id/variables +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|---------------------| +| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables" +``` + +```json +[ + { + "key": "TEST_VARIABLE_1", + "value": "TEST_1" + }, + { + "key": "TEST_VARIABLE_2", + "value": "TEST_2" + } +] +``` + +## Show variable details + +Get the details of a group's specific variable. + +``` +GET /groups/:id/variables/:key +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|-----------------------| +| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `key` | string | yes | The `key` of a variable | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/TEST_VARIABLE_1" +``` + +```json +{ + "key": "TEST_VARIABLE_1", + "value": "TEST_1" +} +``` + +## Create variable + +Create a new variable. + +``` +POST /groups/:id/variables +``` + +| Attribute | Type | required | Description | +|-------------|---------|----------|-----------------------| +| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed | +| `value` | string | yes | The `value` of a variable | +| `protected` | boolean | no | Whether the variable is protected | + +``` +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" +``` + +```json +{ + "key": "NEW_VARIABLE", + "value": "new value", + "protected": false +} +``` + +## Update variable + +Update a group's variable. + +``` +PUT /groups/:id/variables/:key +``` + +| Attribute | Type | required | Description | +|-------------|---------|----------|-------------------------| +| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `key` | string | yes | The `key` of a variable | +| `value` | string | yes | The `value` of a variable | +| `protected` | boolean | no | Whether the variable is protected | + +``` +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/NEW_VARIABLE" --form "value=updated value" +``` + +```json +{ + "key": "NEW_VARIABLE", + "value": "updated value", + "protected": true +} +``` + +## Remove variable + +Remove a group's variable. + +``` +DELETE /groups/:id/variables/:key +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|-------------------------| +| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `key` | string | yes | The `key` of a variable | + +``` +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1" +``` diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md index 086fba7e91d..dbfc7529125 100644 --- a/doc/api/group_milestones.md +++ b/doc/api/group_milestones.md @@ -6,7 +6,7 @@ Returns a list of group milestones. ``` GET /groups/:id/milestones -GET /groups/:id/milestones?iids=42 +GET /groups/:id/milestones?iids[]=42 GET /groups/:id/milestones?iids[]=42&iids[]=43 GET /groups/:id/milestones?state=active GET /groups/:id/milestones?state=closed @@ -18,7 +18,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | -| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` | +| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` | | `state` | string | optional | Return only `active` or `closed` milestones` | | `search` | string | optional | Return only milestones with a title or description matching the provided string | diff --git a/doc/api/issues.md b/doc/api/issues.md index 6bac2927339..f30ed08d0fa 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -40,7 +40,7 @@ GET /issues?assignee_id=5 | `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | -| `iids` | Array[integer] | no | Return only the issues having the given `iid` | +| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search issues against their `title` and `description` | @@ -132,7 +132,7 @@ GET /groups/:id/issues?assignee_id=5 | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | -| `iids` | Array[integer] | no | Return only the issues having the given `iid` | +| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `milestone` | string | no | The milestone title | | `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | @@ -227,7 +227,7 @@ GET /projects/:id/issues?assignee_id=5 | Attribute | Type | Required | Description | |-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `iids` | Array[integer] | no | Return only the milestone having the given `iid` | +| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index d0725b5e06e..802e5362d70 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -117,7 +117,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `iids` | Array[integer] | no | Return the request having the given `iid` | +| `iids[]` | Array[integer] | no | Return the request having the given `iid` | | `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged`| | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | diff --git a/doc/api/milestones.md b/doc/api/milestones.md index a082d548499..84930f0bdc9 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -6,7 +6,7 @@ Returns a list of project milestones. ``` GET /projects/:id/milestones -GET /projects/:id/milestones?iids=42 +GET /projects/:id/milestones?iids[]=42 GET /projects/:id/milestones?iids[]=42&iids[]=43 GET /projects/:id/milestones?state=active GET /projects/:id/milestones?state=closed @@ -18,7 +18,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` | +| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` | | `state` | string | optional | Return only `active` or `closed` milestones` | | `search` | string | optional | Return only milestones with a title or description matching the provided string | diff --git a/doc/api/notes.md b/doc/api/notes.md index 388e6989df2..e627369e17b 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -35,7 +35,8 @@ Parameters: "updated_at": "2013-10-02T10:22:45Z", "system": true, "noteable_id": 377, - "noteable_type": "Issue" + "noteable_type": "Issue", + "noteable_iid": 377 }, { "id": 305, @@ -53,7 +54,8 @@ Parameters: "updated_at": "2013-10-02T09:56:03Z", "system": true, "noteable_id": 121, - "noteable_type": "Issue" + "noteable_type": "Issue", + "noteable_iid": 121 } ] ``` @@ -267,7 +269,8 @@ Parameters: "updated_at": "2013-10-02T08:57:14Z", "system": false, "noteable_id": 2, - "noteable_type": "MergeRequest" + "noteable_type": "MergeRequest", + "noteable_iid": 2 } ``` diff --git a/doc/api/build_variables.md b/doc/api/project_level_variables.md index d4f00256ed3..82ac0b09027 100644 --- a/doc/api/build_variables.md +++ b/doc/api/project_level_variables.md @@ -1,8 +1,8 @@ -# Build Variables API +# Project-level Variables API ## List project variables -Get list of a project's build variables. +Get list of a project's variables. ``` GET /projects/:id/variables @@ -31,7 +31,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ ## Show variable details -Get the details of a project's specific build variable. +Get the details of a project's specific variable. ``` GET /projects/:id/variables/:key @@ -55,7 +55,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ ## Create variable -Create a new build variable. +Create a new variable. ``` POST /projects/:id/variables @@ -82,7 +82,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl ## Update variable -Update a project's build variable. +Update a project's variable. ``` PUT /projects/:id/variables/:key @@ -109,7 +109,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitla ## Remove variable -Remove a project's build variable. +Remove a project's variable. ``` DELETE /projects/:id/variables/:key diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md new file mode 100644 index 00000000000..10faa95d7e8 --- /dev/null +++ b/doc/api/protected_branches.md @@ -0,0 +1,145 @@ +# Protected branches API + +>**Note:** This feature was introduced in GitLab 9.5 + +**Valid access levels** + +The access levels are defined in the `ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS` constant. Currently, these levels are recognized: +``` +0 => No access +30 => Developer access +40 => Master access +``` + +## List protected branches + +Gets a list of protected branches from a project. + +``` +GET /projects/:id/protected_branches +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches' +``` + +Example response: + +```json +[ + { + "name": "master", + "push_access_levels": [ + { + "access_level": 40, + "access_level_description": "Masters" + } + ], + "merge_access_levels": [ + { + "access_level": 40, + "access_level_description": "Masters" + } + ] + }, + ... +] +``` + +## Get a single protected branch or wildcard protected branch + +Gets a single protected branch or wildcard protected branch. + +``` +GET /projects/:id/protected_branches/:name +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the branch or wildcard | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/master' +``` + +Example response: + +```json +{ + "name": "master", + "push_access_levels": [ + { + "access_level": 40, + "access_level_description": "Masters" + } + ], + "merge_access_levels": [ + { + "access_level": 40, + "access_level_description": "Masters" + } + ] +} +``` + +## Protect repository branches + +Protects a single repository branch or several project repository +branches using a wildcard protected branch. + +``` +POST /projects/:id/protected_branches +``` + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&push_access_level=30&merge_access_level=30' +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the branch or wildcard | +| `push_access_level` | string | no | Access levels allowed to push (defaults: `40`, master access level) | +| `merge_access_level` | string | no | Access levels allowed to merge (defaults: `40`, master access level) | + +Example response: + +```json +{ + "name": "*-stable", + "push_access_levels": [ + { + "access_level": 30, + "access_level_description": "Developers + Masters" + } + ], + "merge_access_levels": [ + { + "access_level": 30, + "access_level_description": "Developers + Masters" + } + ] +} +``` + +## Unprotect repository branches + +Unprotects the given protected branch or wildcard protected branch. + +``` +DELETE /projects/:id/protected_branches/:name +``` + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/*-stable' +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the branch | diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 1fc577561a0..c517a38a8ba 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -76,7 +76,8 @@ Example response: Parameters: - `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb -- `branch` (required) - The name of branch +- `branch` (required) - Name of the branch +- `start_branch` (optional) - Name of the branch to start the new commit from - `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name @@ -105,7 +106,8 @@ Example response: Parameters: - `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb -- `branch` (required) - The name of branch +- `branch` (required) - Name of the branch +- `start_branch` (optional) - Name of the branch to start the new commit from - `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name @@ -144,7 +146,8 @@ Example response: Parameters: - `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb -- `branch` (required) - The name of branch +- `branch` (required) - Name of the branch +- `start_branch` (optional) - Name of the branch to start the new commit from - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `commit_message` (required) - Commit message diff --git a/doc/api/repository_storage_health.md b/doc/api/repository_storage_health.md new file mode 100644 index 00000000000..e0c0315c2d7 --- /dev/null +++ b/doc/api/repository_storage_health.md @@ -0,0 +1,74 @@ +# Circuitbreaker API + +> [Introduced][ce-11449] in GitLab 9.5. + +The Circuitbreaker API is only accessible to administrators. All requests by +guests will respond with `401 Unauthorized`, and all requests by normal users +will respond with `403 Forbidden`. + +## Repository Storages + +### Get all storage information + +Returns of all currently configured storages and their health information. + +``` +GET /circuit_breakers/repository_storage +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage +``` + +```json +[ + { + "storage_name": "default", + "failing_on_hosts": [], + "total_failures": 0 + }, + { + "storage_name": "broken", + "failing_on_hosts": [ + "web01", "worker01" + ], + "total_failures": 1 + } +] +``` + +### Get failing storages + +This returns a list of all currently failing storages. + +``` +GET /circuit_breakers/repository_storage/failing +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage/failing +``` + +```json +[ + { + "storage_name":"broken", + "failing_on_hosts":["web01", "worker01"], + "total_failures":2 + } +] +``` + +## Reset failing storage information + +Use this remove all failing storage information and allow access to the storage again. + +``` +DELETE /circuit_breakers/repository_storage +``` + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage +``` + +[ce-11449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11449 diff --git a/doc/api/tags.md b/doc/api/tags.md index 54f092d1d30..32fe5eea692 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -18,17 +18,20 @@ Parameters: [ { "commit": { + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "short_id": "2695effb", + "title": "Initial commit", + "created_at": "2017-07-26T11:08:53.000+02:00", + "parent_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ], + "message": "Initial commit", "author_name": "John Smith", "author_email": "john@example.com", "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", "committer_name": "Jack Smith", "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parent_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] + "committed_date": "2012-05-28T04:42:42-07:00" }, "release": { "tag_name": "1.0.0", @@ -68,16 +71,19 @@ Example Response: "message": null, "commit": { "id": "60a8ff033665e1207714d6670fcd7b65304ec02f", - "message": "v5.0.0\n", + "short_id": "60a8ff03", + "title": "Initial commit", + "created_at": "2017-07-26T11:08:53.000+02:00", "parent_ids": [ "f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b" ], - "authored_date": "2015-02-01T21:56:31.000+01:00", + "message": "v5.0.0\n", "author_name": "Arthur Verschaeve", "author_email": "contact@arthurverschaeve.be", - "committed_date": "2015-02-01T21:56:31.000+01:00", + "authored_date": "2015-02-01T21:56:31.000+01:00", "committer_name": "Arthur Verschaeve", - "committer_email": "contact@arthurverschaeve.be" + "committer_email": "contact@arthurverschaeve.be", + "committed_date": "2015-02-01T21:56:31.000+01:00" }, "release": null } @@ -102,17 +108,20 @@ Parameters: ```json { "commit": { + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "short_id": "2695effb", + "title": "Initial commit", + "created_at": "2017-07-26T11:08:53.000+02:00", + "parent_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ], + "message": "Initial commit", "author_name": "John Smith", "author_email": "john@example.com", "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", "committer_name": "Jack Smith", "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parent_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] + "committed_date": "2012-05-28T04:42:42-07:00" }, "release": { "tag_name": "1.0.0", diff --git a/doc/articles/index.md b/doc/articles/index.md index a4e41517d83..558c624fe39 100644 --- a/doc/articles/index.md +++ b/doc/articles/index.md @@ -7,40 +7,107 @@ to provide the community with guidance on specific processes to achieve certain They are written by members of the GitLab Team and by [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/). +Part of the articles listed below link to the [GitLab Blog](https://about.gitlab.com/blog/), +where they were originally published. + ## Authentication -- **LDAP** - - [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md) - - [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) +Explore GitLab's supported [authentications methods](../topics/authentication/index.md): + +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| **LDAP** | +| [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)| Admin guide | 2017/05/03 | +| [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) | Admin guide | 2017/05/03 | + +## Build, test, and deploy with GitLab CI/CD + +Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/README.md): + +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 | +| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017/07/11 | +| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017/07/27 | +| [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) | Tutorial | 2016/12/14 | +| [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) | Tutorial | 2016/11/30 | +| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016/10/12 | +| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016/08/11 | +| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016/06/09 | +| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016/05/23 | +| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017/05/15 | +| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016/03/10 | ## Git -- [How to install Git](how_to_install_git/index.md) +Learn how to use [Git with GitLab](../topics/git/index.md): + +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/) | Concepts | 2017/05/17 | +| [How to install Git](how_to_install_git/index.md) | Tutorial | 2017/05/15 | +| [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/) | Tutorial | 2017/01/30 | +| [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/) | Technical overview | 2016/12/08 | ## GitLab Pages -- **GitLab Pages from A to Z** - - [Part 1: Static sites and GitLab Pages domains](../user/project/pages/getting_started_part_one.md) - - [Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md) - - [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md) - - [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md) -- [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) -- [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) +Learn how to deploy a static website with [GitLab Pages](../user/project/pages/index.md#getting-started): -## Sofware development +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| **Series: GitLab Pages from A to Z:** | +| [- Part 1: Static sites and GitLab Pages domains](../user/project/pages/getting_started_part_one.md)| User guide | 2017/02/22 | +| [- Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)| User guide | 2017/02/22 | +| [- Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)| User guide | 2017/02/22 | +| [- Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)| User guide | 2017/02/22 | +| [Setting up GitLab Pages with CloudFlare Certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Tutorial | 2017/02/07 | +| [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) | Tutorial | 2016/12/07 | +| [Publish Code Coverage Report with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/) | Tutorial | 2016/11/03 | +| [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) | Tutorial | 2016/08/26 | +| [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) | Tutorial | 2016/08/19 | +| **Series: Static Site Generator:** | +| [- Part 1: Dynamic vs Static Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | Tutorial | 2016/06/03 | +| [- Part 2: Modern Static Site Generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | Tutorial | 2016/06/10 | +| [- Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) | Tutorial | 2016/06/17 | +| [Securing your GitLab Pages with TLS and Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) | Tutorial | 2016/04/11 | +| [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) | Tutorial | 2016/04/07 | -- [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) -- [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) -- [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) -- [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) -- [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) +## Install and maintain GitLab -## Build, test, and deploy with GitLab CI/CD +Install, upgrade, integrate, migrate to GitLab: + +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017/01/23 | +| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016/07/13 | +| [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016/06/28 | +| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016/04/27 | + +## Software development + +Explore the best of GitLab's software development's capabilities: + +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 | +| [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/)| Concepts | 2017/06/29 | +| [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) | Concepts | 2017/05/22 | +| [Demo: Auto-Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) | Technical overview | 2017/05/16 | +| [Demo: GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/) | Feature highlight | 2017/05/09 | +| [Demo: Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017/04/25 | +| [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017/04/18 | +| [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017/03/17 | +| [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) | Technical overview | 2016/11/14 | +| [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Technical overview | 2016/10/25 | +| [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/) | Concepts | 2016/08/16 | +| [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) | Concepts | 2016/08/05 | +| [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/) | Concepts | 2016/07/07 | +| [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/) | Technical overview | 2016/03/08 | -**Build, test, and deploy** the software you develop with **[GitLab CI/CD](../ci/README.md)** +## Technologies -- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) -- [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) -- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) -- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) -- [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) +| Article title | Category | Publishing date | +| :------------ | :------: | --------------: | +| [Why we are not leaving the cloud](https://about.gitlab.com/2017/03/02/why-we-are-not-leaving-the-cloud/) | Concepts | 2017/03/02 | +| [Why We Chose Vue.js](https://about.gitlab.com/2016/10/20/why-we-chose-vue/) | Concepts | 2016/10/20 | +| [Markdown Kramdown Tips & Tricks](https://about.gitlab.com/2016/07/19/markdown-kramdown-tips-and-tricks/) | Technical overview | 2016/07/19 | diff --git a/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png b/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png Binary files differnew file mode 100644 index 00000000000..fcad4e59ae3 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png diff --git a/doc/articles/openshift_and_gitlab/img/add-to-project.png b/doc/articles/openshift_and_gitlab/img/add-to-project.png Binary files differnew file mode 100644 index 00000000000..bd915a229f6 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/add-to-project.png diff --git a/doc/articles/openshift_and_gitlab/img/create-project-ui.png b/doc/articles/openshift_and_gitlab/img/create-project-ui.png Binary files differnew file mode 100644 index 00000000000..e72866f252a --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/create-project-ui.png diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-logs.png b/doc/articles/openshift_and_gitlab/img/gitlab-logs.png Binary files differnew file mode 100644 index 00000000000..1e24080c7df --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/gitlab-logs.png diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-overview.png b/doc/articles/openshift_and_gitlab/img/gitlab-overview.png Binary files differnew file mode 100644 index 00000000000..3c5df0ea101 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/gitlab-overview.png diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-running.png b/doc/articles/openshift_and_gitlab/img/gitlab-running.png Binary files differnew file mode 100644 index 00000000000..c7db691cb30 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/gitlab-running.png diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-scale.png b/doc/articles/openshift_and_gitlab/img/gitlab-scale.png Binary files differnew file mode 100644 index 00000000000..4903c7d7498 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/gitlab-scale.png diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-settings.png b/doc/articles/openshift_and_gitlab/img/gitlab-settings.png Binary files differnew file mode 100644 index 00000000000..db4360ffef0 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/gitlab-settings.png diff --git a/doc/articles/openshift_and_gitlab/img/no-resources.png b/doc/articles/openshift_and_gitlab/img/no-resources.png Binary files differnew file mode 100644 index 00000000000..480fb766468 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/no-resources.png diff --git a/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png b/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png Binary files differnew file mode 100644 index 00000000000..8b9f85aa341 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png diff --git a/doc/articles/openshift_and_gitlab/img/pods-overview.png b/doc/articles/openshift_and_gitlab/img/pods-overview.png Binary files differnew file mode 100644 index 00000000000..e1cf08bd217 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/pods-overview.png diff --git a/doc/articles/openshift_and_gitlab/img/rc-name.png b/doc/articles/openshift_and_gitlab/img/rc-name.png Binary files differnew file mode 100644 index 00000000000..889e34adbec --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/rc-name.png diff --git a/doc/articles/openshift_and_gitlab/img/running-pods.png b/doc/articles/openshift_and_gitlab/img/running-pods.png Binary files differnew file mode 100644 index 00000000000..3fd4e56662f --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/running-pods.png diff --git a/doc/articles/openshift_and_gitlab/img/storage-volumes.png b/doc/articles/openshift_and_gitlab/img/storage-volumes.png Binary files differnew file mode 100644 index 00000000000..ae1e5381faa --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/storage-volumes.png diff --git a/doc/articles/openshift_and_gitlab/img/web-console.png b/doc/articles/openshift_and_gitlab/img/web-console.png Binary files differnew file mode 100644 index 00000000000..aa1425d4f94 --- /dev/null +++ b/doc/articles/openshift_and_gitlab/img/web-console.png diff --git a/doc/articles/openshift_and_gitlab/index.md b/doc/articles/openshift_and_gitlab/index.md new file mode 100644 index 00000000000..7f76e577efa --- /dev/null +++ b/doc/articles/openshift_and_gitlab/index.md @@ -0,0 +1,510 @@ +# Getting started with OpenShift Origin 3 and GitLab + +> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial || +> **Level:** intermediary || +> **Author:** [Achilleas Pipinellis](https://gitlab.com/axil) || +> **Publication date:** 2016/06/28 + +## Introduction + +[OpenShift Origin][openshift] is an open source container application +platform created by [RedHat], based on [kubernetes] and [Docker]. That means +you can host your own PaaS for free and almost with no hassle. + +In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's +official Docker image while getting familiar with the web interface and CLI +tools that will help us achieve our goal. + +--- + +## Prerequisites + +OpenShift 3 is not yet deployed on RedHat's offered Online platform ([openshift.com]), +so in order to test it, we will use an [all-in-one Virtualbox image][vm] that is +offered by the OpenShift developers and managed by Vagrant. If you haven't done +already, go ahead and install the following components as they are essential to +test OpenShift easily: + +- [VirtualBox] +- [Vagrant] +- [OpenShift Client][oc] (`oc` for short) + +It is also important to mention that for the purposes of this tutorial, the +latest Origin release is used: + +- **oc** `v1.3.0` (must be [installed][oc-gh] locally on your computer) +- **openshift** `v1.3.0` (is pre-installed in the [VM image][vm-new]) +- **kubernetes** `v1.3.0` (is pre-installed in the [VM image][vm-new]) + +>**Note:** +If you intend to deploy GitLab on a production OpenShift cluster, there are some +limitations to bare in mind. Read on the [limitations](#current-limitations) +section for more information and follow the linked links for the relevant +discussions. + +Now that you have all batteries, let's see how easy it is to test OpenShift +on your computer. + +## Getting familiar with OpenShift Origin + +The environment we are about to use is based on CentOS 7 which comes with all +the tools needed pre-installed: Docker, kubernetes, OpenShift, etcd. + +### Test OpenShift using Vagrant + +As of this writing, the all-in-one VM is at version 1.3, and that's +what we will use in this tutorial. + +In short: + +1. Open a terminal and in a new directory run: + ```sh + vagrant init openshift/origin-all-in-one + ``` +1. This will generate a Vagrantfile based on the all-in-one VM image +1. In the same directory where you generated the Vagrantfile + enter: + + ```sh + vagrant up + ``` + +This will download the VirtualBox image and fire up the VM with some preconfigured +values as you can see in the Vagrantfile. As you may have noticed, you need +plenty of RAM (5GB in our example), so make sure you have enough. + +Now that OpenShift is setup, let's see how the web console looks like. + +### Explore the OpenShift web console + +Once Vagrant finishes its thing with the VM, you will be presented with a +message which has some important information. One of them is the IP address +of the deployed OpenShift platform and in particular <https://10.2.2.2:8443/console/>. +Open this link with your browser and accept the self-signed certificate in +order to proceed. + +Let's login as admin with username/password `admin/admin`. This is what the +landing page looks like: + +![openshift web console](img/web-console.png) + +You can see that a number of [projects] are already created for testing purposes. + +If you head over the `openshift-infra` project, a number of services with their +respective pods are there to explore. + +![openshift web console](img/openshift-infra-project.png) + +We are not going to explore the whole interface, but if you want to learn about +the key concepts of OpenShift, read the [core concepts reference][core] in the +official documentation. + +### Explore the OpenShift CLI + +OpenShift Client (`oc`), is a powerful CLI tool that talks to the OpenShift API +and performs pretty much everything you can do from the web UI and much more. + +Assuming you have [installed][oc] it, let's explore some of its main +functionalities. + +Let's first see the version of `oc`: + +```sh +$ oc version + +oc v1.3.0 +kubernetes v1.3.0+52492b4 +``` + +With `oc help` you can see the top level arguments you can run with `oc` and +interact with your cluster, kubernetes, run applications, create projects and +much more. + +Let's login to the all-in-one VM and see how to achieve the same results like +when we visited the web console earlier. The username/password for the +administrator user is `admin/admin`. There is also a test user with username/ +password `user/user`, with limited access. Let's login as admin for the moment: + +```sh +$ oc login https://10.2.2.2:8443 + +Authentication required for https://10.2.2.2:8443 (openshift) +Username: admin +Password: +Login successful. + +You have access to the following projects and can switch between them with 'oc project <projectname>': + + * cockpit + * default (current) + * delete + * openshift + * openshift-infra + * sample + +Using project "default". +``` + +Switch to the `openshift-infra` project with: + +```sh +oc project openshift-infra +``` + +And finally, see its status: + +```sh +oc status +``` + +The last command should spit a bunch of information about the statuses of the +pods and the services, which if you look closely is what we encountered in the +second image when we explored the web console. + +You can always read more about `oc` in the [OpenShift CLI documentation][oc]. + +### Troubleshooting the all-in-one VM + +Using the all-in-one VM gives you the ability to test OpenShift whenever you +want. That means you get to play with it, shutdown the VM, and pick up where +you left off. + +Sometimes though, you may encounter some issues, like OpenShift not running +when booting up the VM. The web UI may not responding or you may see issues +when trying to login with `oc`, like: + +``` +The connection to the server 10.2.2.2:8443 was refused - did you specify the right host or port? +``` + +In that case, the OpenShift service might not be running, so in order to fix it: + +1. SSH into the VM by going to the directory where the Vagrantfile is and then + run: + + ```sh + vagrant ssh + ``` + +1. Run `systemctl` and verify by the output that the `openshift` service is not + running (it will be in red color). If that's the case start the service with: + + ```sh + sudo systemctl start openshift + ``` + +1. Verify the service is up with: + + ```sh + systemctl status openshift -l + ``` + +Now you will be able to login using `oc` (like we did before) and visit the web +console. + +## Deploy GitLab + +Now that you got a taste of what OpenShift looks like, let's deploy GitLab! + +### Create a new project + +First, we will create a new project to host our application. You can do this +either by running the CLI client: + +```bash +$ oc new-project gitlab +``` + +or by using the web interface: + +![Create a new project from the UI](img/create-project-ui.png) + +If you used the command line, `oc` automatically uses the new project and you +can see its status with: + +```sh +$ oc status + +In project gitlab on server https://10.2.2.2:8443 + +You have no services, deployment configs, or build configs. +Run 'oc new-app' to create an application. +``` + +If you visit the web console, you can now see `gitlab` listed in the projects list. + +The next step is to import the OpenShift template for GitLab. + +### Import the template + +The [template][templates] is basically a JSON file which describes a set of +related object definitions to be created together, as well as a set of +parameters for those objects. + +The template for GitLab resides in the Omnibus GitLab repository under the +docker directory. Let's download it locally with `wget`: + +```bash +wget https://gitlab.com/gitlab-org/omnibus-gitlab/raw/master/docker/openshift-template.json +``` + +And then let's import it in OpenShift: + +```bash +oc create -f openshift-template.json -n openshift +``` + +>**Note:** +The `-n openshift` namespace flag is a trick to make the template available to all +projects. If you recall from when we created the `gitlab` project, `oc` switched +to it automatically, and that can be verified by the `oc status` command. If +you omit the namespace flag, the application will be available only to the +current project, in our case `gitlab`. The `openshift` namespace is a global +one that the administrators should use if they want the application to be +available to all users. + +We are now ready to finally deploy GitLab! + +### Create a new application + +The next step is to use the template we previously imported. Head over to the +`gitlab` project and hit the **Add to Project** button. + +![Add to project](img/add-to-project.png) + +This will bring you to the catalog where you can find all the pre-defined +applications ready to deploy with the click of a button. Search for `gitlab` +and you will see the previously imported template: + +![Add GitLab to project](img/add-gitlab-to-project.png) + +Select it, and in the following screen you will be presented with the predefined +values used with the GitLab template: + +![GitLab settings](img/gitlab-settings.png) + +Notice at the top that there are three resources to be created with this +template: + +- `gitlab-ce` +- `gitlab-ce-redis` +- `gitlab-ce-postgresql` + +While PostgreSQL and Redis are bundled in Omnibus GitLab, the template is using +separate images as you can see from [this line][line] in the template. + +The predefined values have been calculated for the purposes of testing out +GitLab in the all-in-one VM. You don't need to change anything here, hit +**Create** to start the deployment. + +If you are deploying to production you will want to change the **GitLab instance +hostname** and use greater values for the volume sizes. If you don't provide a +password for PostgreSQL, it will be created automatically. + +>**Note:** +The `gitlab.apps.10.2.2.2.xip.io` hostname that is used by default will +resolve to the host with IP `10.2.2.2` which is the IP our VM uses. It is a +trick to have distinct FQDNs pointing to services that are on our local network. +Read more on how this works in <http://xip.io>. + +Now that we configured this, let's see how to manage and scale GitLab. + +## Manage and scale GitLab + +Setting up GitLab for the first time might take a while depending on your +internet connection and the resources you have attached to the all-in-one VM. +GitLab's docker image is quite big (~500MB), so you'll have to wait until +it's downloaded and configured before you use it. + +### Watch while GitLab gets deployed + +Navigate to the `gitlab` project at **Overview**. You can notice that the +deployment is in progress by the orange color. The Docker images are being +downloaded and soon they will be up and running. + +![GitLab overview](img/gitlab-overview.png) + +Switch to the **Browse > Pods** and you will eventually see all 3 pods in a +running status. Remember the 3 resources that were to be created when we first +created the GitLab app? This is where you can see them in action. + +![Running pods](img/running-pods.png) + +You can see GitLab being reconfigured by taking look at the logs in realtime. +Click on `gitlab-ce-2-j7ioe` (your ID will be different) and go to the **Logs** +tab. + +![GitLab logs](img/gitlab-logs.png) + +At a point you should see a _**gitlab Reconfigured!**_ message in the logs. +Navigate back to the **Overview** and hopefully all pods will be up and running. + +![GitLab running](img/gitlab-running.png) + +Congratulations! You can now navigate to your new shinny GitLab instance by +visiting <http://gitlab.apps.10.2.2.2.xip.io> where you will be asked to +change the root user password. Login using `root` as username and providing the +password you just set, and start using GitLab! + +### Scale GitLab with the push of a button + +If you reach to a point where your GitLab instance could benefit from a boost +of resources, you'd be happy to know that you can scale up with the push of a +button. + +In the **Overview** page just click the up arrow button in the pod where +GitLab is. The change is instant and you can see the number of [replicas] now +running scaled to 2. + +![GitLab scale](img/gitlab-scale.png) + +Upping the GitLab pods is actually like adding new application servers to your +cluster. You can see how that would work if you didn't use GitLab with +OpenShift by following the [HA documentation][ha] for the application servers. + +Bare in mind that you may need more resources (CPU, RAM, disk space) when you +scale up. If a pod is in pending state for too long, you can navigate to +**Browse > Events** and see the reason and message of the state. + +![No resources](img/no-resources.png) + +### Scale GitLab using the `oc` CLI + +Using `oc` is super easy to scale up the replicas of a pod. You may want to +skim through the [basic CLI operations][basic-cli] to get a taste how the CLI +commands are used. Pay extra attention to the object types as we will use some +of them and their abbreviated versions below. + +In order to scale up, we need to find out the name of the replication controller. +Let's see how to do that using the following steps. + +1. Make sure you are in the `gitlab` project: + + ```sh + oc project gitlab + ``` + +1. See what services are used for this project: + + ```sh + oc get svc + ``` + + The output will be similar to: + + ``` + NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE + gitlab-ce 172.30.243.177 <none> 22/TCP,80/TCP 5d + gitlab-ce-postgresql 172.30.116.75 <none> 5432/TCP 5d + gitlab-ce-redis 172.30.105.88 <none> 6379/TCP 5d + ``` + +1. We need to see the replication controllers of the `gitlab-ce` service. + Get a detailed view of the current ones: + + ```sh + oc describe rc gitlab-ce + ``` + + This will return a large detailed list of the current replication controllers. + Search for the name of the GitLab controller, usually `gitlab-ce-1` or if + that failed at some point and you spawned another one, it will be named + `gitlab-ce-2`. + +1. Scale GitLab using the previous information: + + ```sh + oc scale --replicas=2 replicationcontrollers gitlab-ce-2 + ``` + +1. Get the new replicas number to make sure scaling worked: + + ```sh + oc get rc gitlab-ce-2 + ``` + + which will return something like: + + ``` + NAME DESIRED CURRENT AGE + gitlab-ce-2 2 2 5d + ``` + +And that's it! We successfully scaled the replicas to 2 using the CLI. + +As always, you can find the name of the controller using the web console. Just +click on the service you are interested in and you will see the details in the +right sidebar. + +![Replication controller name](img/rc-name.png) + +### Autoscaling GitLab + +In case you were wondering whether there is an option to autoscale a pod based +on the resources of your server, the answer is yes, of course there is. + +We will not expand on this matter, but feel free to read the documentation on +OpenShift's website about [autoscaling]. + +## Current limitations + +As stated in the [all-in-one VM][vm] page: + +> By default, OpenShift will not allow a container to run as root or even a +non-random container assigned userid. Most Docker images in the Dockerhub do not +follow this best practice and instead run as root. + +The all-in-one VM we are using has this security turned off so it will not +bother us. In any case, it is something to keep in mind when deploying GitLab +on a production cluster. + +In order to deploy GitLab on a production cluster, you will need to assign the +GitLab service account to the `anyuid` Security Context. + +1. Edit the Security Context: + ```sh + oc edit scc anyuid + ``` + +1. Add `system:serviceaccount:<project>:gitlab-ce-user` to the `users` section. + If you changed the Application Name from the default the user will + will be `<app-name>-user` instead of `gitlab-ce-user` + +1. Save and exit the editor + +## Conclusion + +By now, you should have an understanding of the basic OpenShift Origin concepts +and a sense of how things work using the web console or the CLI. + +GitLab was hard to install in previous versions of OpenShift, +but now that belongs to the past. Upload a template, create a project, add an +application and you are done. You are ready to login to your new GitLab instance. + +And remember that in this tutorial we just scratched the surface of what Origin +is capable of. As always, you can refer to the detailed +[documentation][openshift-docs] to learn more about deploying your own OpenShift +PaaS and managing your applications with the ease of containers. + +[RedHat]: https://www.redhat.com/en "RedHat website" +[openshift]: https://www.openshift.org "OpenShift Origin website" +[vm]: https://www.openshift.org/vm/ "OpenShift All-in-one VM" +[vm-new]: https://atlas.hashicorp.com/openshift/boxes/origin-all-in-one "Official OpenShift Vagrant box on Atlas" +[template]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/docker/openshift-template.json "OpenShift template for GitLab" +[openshift.com]: https://openshift.com "OpenShift Online" +[kubernetes]: http://kubernetes.io/ "Kubernetes website" +[Docker]: https://www.docker.com "Docker website" +[oc]: https://docs.openshift.org/latest/cli_reference/get_started_cli.html "Documentation - oc CLI documentation" +[VirtualBox]: https://www.virtualbox.org/wiki/Downloads "VirtualBox downloads" +[Vagrant]: https://www.vagrantup.com/downloads.html "Vagrant downloads" +[projects]: https://docs.openshift.org/latest/dev_guide/projects.html "Documentation - Projects overview" +[core]: https://docs.openshift.org/latest/architecture/core_concepts/index.html "Documentation - Core concepts of OpenShift Origin" +[templates]: https://docs.openshift.org/latest/architecture/core_concepts/templates.html "Documentation - OpenShift templates" +[old-post]: https://blog.openshift.com/deploy-gitlab-openshift/ "Old post - Deploy GitLab on OpenShift" +[line]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/658c065c8d022ce858dd63eaeeadb0b2ddc8deea/docker/openshift-template.json#L239 "GitLab - OpenShift template" +[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "Openshift 1.3.0 release on GitHub" +[ha]: http://docs.gitlab.com/ce/administration/high_availability/gitlab.html "Documentation - GitLab High Availability" +[replicas]: https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers "Documentation - Replication controller" +[autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale" +[basic-cli]: https://docs.openshift.org/latest/cli_reference/basic_cli_operations.html "Documentation - Basic CLI operations" +[openshift-docs]: https://docs.openshift.org "OpenShift documentation" diff --git a/doc/ci/README.md b/doc/ci/README.md index ca7266ac68f..10ea9467942 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -23,6 +23,7 @@ The first steps towards your GitLab CI journey. - [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) - [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) - **Videos:** + - [Demo (Streamed live on Jul 17, 2017): GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195) - [Demo (March, 2017): how to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/) - [Webcast (April, 2016): getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/) - **Third-party videos:** diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index e67db9ff142..f83a60e49e8 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -7,6 +7,11 @@ storing data in a single JSON column the data is stored in a separate table. ## When To Use Background Migrations +>**Note:** +When adding background migrations _you must_ make sure they are announced in the +monthly release post along with an estimate of how long it will take to complete +the migrations. + In the vast majority of cases you will want to use a regular Rails migration instead. Background migrations should _only_ be used when migrating _data_ in tables that have so many rows this process would take hours when performed in a @@ -91,6 +96,10 @@ BackgroundMigrationWorker.perform_bulk_in(5.minutes, jobs) ## Cleaning Up +>**Note:** +Cleaning up any remaining background migrations _must_ be done in either a major +or minor release, you _must not_ do this in a patch release. + Because background migrations can take a long time you can't immediately clean things up after scheduling them. For example, you can't drop a column that's used in the migration process as this would cause jobs to fail. This means that diff --git a/doc/development/code_review.md b/doc/development/code_review.md index e3f37616757..64a89976300 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -11,6 +11,8 @@ There are a few rules to get your merge request accepted: **approved by a [frontend maintainer][projects]**. 1. If your merge request includes frontend and backend changes [^1], it must be **approved by a [frontend and a backend maintainer][projects]**. + 1. If your merge request includes a new dependency or a filesystem change, it must + be **approved by a [Build team member][team]**. See [how to work with the Build team][build handbook] for more details. 1. To lower the amount of merge requests maintainers need to review, you can ask or assign any [reviewers][projects] for a first review. 1. If you need some guidance (e.g. it's your first merge request), feel free @@ -194,3 +196,4 @@ Largely based on the [thoughtbot code review guide]. [projects]: https://about.gitlab.com/handbook/engineering/projects/ [team]: https://about.gitlab.com/team/ +[build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 149a0159680..6ade3231fac 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -11,7 +11,7 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. #### ESlint -1. **Never** disable eslint rules unless you have a good reason. +1. **Never** disable eslint rules unless you have a good reason. You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */` at the top, but legacy files are a special case. Any time you develop a new feature or refactor an existing one, you should abide by the eslint rules. @@ -100,26 +100,44 @@ followed by any global declarations, then a blank newline prior to any imports o export default Foo; ``` -1. Relative paths: Unless you are writing a test, always reference other scripts using -relative paths instead of `~` - * In **app/assets/javascripts**: +1. Relative paths: when importing a module in the same directory, a child +directory, or an immediate parent directory prefer relative paths. When +importing a module which is two or more levels up, prefer either `~/` or `ee/` +. - ```javascript - // bad - import Foo from '~/foo' +In **app/assets/javascripts/my-feature/subdir**: - // good - import Foo from '../foo'; - ``` - * In **spec/javascripts**: +``` javascript +// bad +import Foo from '~/my-feature/foo'; +import Bar from '~/my-feature/subdir/bar'; +import Bin from '~/my-feature/subdir/lib/bin'; - ```javascript - // bad - import Foo from '../../app/assets/javascripts/foo' +// good +import Foo from '../foo'; +import Bar from './bar'; +import Bin from './lib/bin'; +``` - // good - import Foo from '~/foo'; - ``` +In **spec/javascripts**: + +``` javascript +// bad +import Foo from '../../app/assets/javascripts/my-feature/foo'; + +// good +import Foo from '~/my-feature/foo'; +``` + +When referencing an **EE component**: + +``` javascript +// bad +import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo'; + +// good +import Foo from 'ee/my-feature/foo'; +``` 1. Avoid using IIFE. Although we have a lot of examples of files which wrap their contents in IIFEs (immediately-invoked function expressions), @@ -465,7 +483,7 @@ A forEach will cause side effects, it will be mutating the array being iterated. #### Vue and Boostrap 1. Tooltips: Do not rely on `has-tooltip` class name for Vue components - ```javascript + ```javascript // bad <span class="has-tooltip" diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 161d2544169..9b8ab5da74e 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -35,12 +35,11 @@ Please don't depend on GitLab-specific code since it can change in future versions. If needed copy-paste GitLab code into the migration to make it forward compatible. -## Commit Guidelines +## Schema Changes -Each migration **must** be added in its own commit with a descriptive commit -message. If a commit adds a migration it _should only_ include the migration and -any corresponding changes to `db/schema.rb`. This makes it easy to revert a -database migration without accidentally reverting other changes. +Migrations that make changes to the database schema (e.g. adding a column) can +only be added in the monthly release, patch releases may only contain data +migrations _unless_ schema changes are absolutely required to solve a problem. ## Downtime Tagging @@ -224,9 +223,9 @@ add_column(:projects, :foo, :integer, default: 10, limit: 8) ## Timestamp column type -By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information. -The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method. -Also Rails converts the `:datetime` data type to the `timestamp` one. +By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information. +The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method. +Also Rails converts the `:datetime` data type to the `timestamp` one. Example: diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 42bb5e8619c..bfd80aab6a4 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -146,3 +146,20 @@ If new emoji are added, the spritesheet may change size. To compensate for such changes, first generate the `emoji.png` spritesheet with the above Rake task, then check the dimensions of the new spritesheet and update the `SPRITESHEET_WIDTH` and `SPRITESHEET_HEIGHT` constants accordingly. + +## Updating project templates + +Starting a project from a template needs this project to be exported. On a +up to date master branch with run: + +``` +gdk run db +# In a new terminal window +bundle exec rake gitlab:update_project_templates +git checkout -b update-project-templates +git add vendor/project_templates +git commit +git push -u origin update-project-templates +``` + +Now create a merge request and merge that to master. diff --git a/doc/development/testing.md b/doc/development/testing.md index e6aa4ae8f2f..3d5aa3d45e9 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -426,8 +426,6 @@ Here are some things to keep in mind regarding test performance: - `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`. - Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, `spy`, or `double` will do. Database persistence is slow! -- Use `create(:empty_project)` instead of `create(:project)` when you don't need - the underlying Git repository. Filesystem operations are slow! - Don't mark a feature as requiring JavaScript (through `@javascript` in Spinach or `:js` in RSpec) unless it's _actually_ required for the test to be valid. Headless browser testing is slow! diff --git a/doc/install/installation.md b/doc/install/installation.md index 8ded607bcab..e335fc99fbf 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -168,8 +168,10 @@ are out of date, so we'll need to install through the following commands: curl --location https://deb.nodesource.com/setup_7.x | sudo bash - sudo apt-get install -y nodejs - # install yarn - curl --location https://yarnpkg.com/install.sh | bash - + curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - + echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + sudo apt-get update + sudo apt-get install yarn Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps. @@ -294,9 +296,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-4-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-5-stable gitlab -**Note:** You can change `9-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `9-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It @@ -505,15 +507,17 @@ Check if GitLab and its environment are configured correctly: sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +### Compile GetText PO files + + sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production + sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production + ### Compile Assets sudo -u git -H yarn install --production --pure-lockfile sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production -### Compile GetText PO files - - sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production - ### Start Your GitLab Instance sudo service gitlab start diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 583ec5522fd..0399ebec86a 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -31,7 +31,7 @@ X-Gitlab-Event: System Hook "path": "storecloud", "path_with_namespace": "jsmith/storecloud", "project_id": 74, - "project_visibility": "private", + "project_visibility": "private" } ``` @@ -48,7 +48,7 @@ X-Gitlab-Event: System Hook "path": "underscore", "path_with_namespace": "jsmith/underscore", "project_id": 73, - "project_visibility": "internal", + "project_visibility": "internal" } ``` @@ -66,7 +66,7 @@ X-Gitlab-Event: System Hook "owner_name": "John Smith", "owner_email": "johnsmith@gmail.com", "project_visibility": "internal", - "old_path_with_namespace": "jsmith/overscore", + "old_path_with_namespace": "jsmith/overscore" } ``` @@ -84,7 +84,7 @@ X-Gitlab-Event: System Hook "owner_name": "John Smith", "owner_email": "johnsmith@gmail.com", "project_visibility": "internal", - "old_path_with_namespace": "jsmith/overscore", + "old_path_with_namespace": "jsmith/overscore" } ``` @@ -101,7 +101,7 @@ X-Gitlab-Event: System Hook "path": "storecloud", "path_with_namespace": "jsmith/storecloud", "project_id": 74, - "project_visibility": "private", + "project_visibility": "private" } ``` @@ -121,7 +121,7 @@ X-Gitlab-Event: System Hook "user_name": "John Smith", "user_username": "johnsmith", "user_id": 41, - "project_visibility": "private", + "project_visibility": "private" } ``` @@ -141,7 +141,7 @@ X-Gitlab-Event: System Hook "user_name": "John Smith", "user_username": "johnsmith", "user_id": 41, - "project_visibility": "private", + "project_visibility": "private" } ``` diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md index 6308317b1f2..4d3ababaa41 100644 --- a/doc/update/8.17-to-9.0.md +++ b/doc/update/8.17-to-9.0.md @@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage JavaScript dependencies. ```bash -curl --location https://yarnpkg.com/install.sh | bash - +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn ``` More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md index 2d597894517..2b4a7bed27f 100644 --- a/doc/update/9.0-to-9.1.md +++ b/doc/update/9.0-to-9.1.md @@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage JavaScript dependencies. ```bash -curl --location https://yarnpkg.com/install.sh | bash - +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn ``` More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md index 225a4dcc924..6f19d16ad74 100644 --- a/doc/update/9.1-to-9.2.md +++ b/doc/update/9.1-to-9.2.md @@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage JavaScript dependencies. ```bash -curl --location https://yarnpkg.com/install.sh | bash - +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn ``` More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). @@ -97,6 +100,7 @@ cd /home/git/gitlab sudo -u git -H git fetch --all sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale ``` For GitLab Community Edition: @@ -233,6 +237,11 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production +sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production + # Update node dependencies and recompile assets sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md index 910539acc70..9415fa1fcd6 100644 --- a/doc/update/9.2-to-9.3.md +++ b/doc/update/9.2-to-9.3.md @@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage JavaScript dependencies. ```bash -curl --location https://yarnpkg.com/install.sh | bash - +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn ``` More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). @@ -97,6 +100,7 @@ cd /home/git/gitlab sudo -u git -H git fetch --all sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale ``` For GitLab Community Edition: @@ -269,6 +273,10 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production + # Update node dependencies and recompile assets sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md index 9540c36e7d0..982385f3c24 100644 --- a/doc/update/9.3-to-9.4.md +++ b/doc/update/9.3-to-9.4.md @@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage JavaScript dependencies. ```bash -curl --location https://yarnpkg.com/install.sh | bash - +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn ``` More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). @@ -97,6 +100,7 @@ cd /home/git/gitlab sudo -u git -H git fetch --all sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale ``` For GitLab Community Edition: @@ -282,6 +286,10 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production + # Update node dependencies and recompile assets sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production diff --git a/doc/update/9.4-to-9.5.md b/doc/update/9.4-to-9.5.md new file mode 100644 index 00000000000..fc87b2d0f1e --- /dev/null +++ b/doc/update/9.4-to-9.5.md @@ -0,0 +1,352 @@ +# From 9.4 to 9.5 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz +echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz +cd ruby-2.3.3 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and +it has a minimum requirement of node v4.3.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v4.3.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + +<https://nodejs.org/en/download/> + + +Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage +JavaScript dependencies. + +```bash +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 9-5-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 9-5-stable-ee +``` + +### 7. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) +sudo -u git -H bin/compile +``` + +### 8. Update gitlab-workhorse + +Install and compile gitlab-workhorse. GitLab-Workhorse uses +[GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-workhorse + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make +``` + +### 9. Update Gitaly + +#### New Gitaly configuration options required + +In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell'. + +```shell +echo ' +[gitaly-ruby] +dir = "/home/git/gitaly/ruby" + +[gitlab-shell] +dir = "/home/git/gitlab-shell" +' | sudo -u git tee -a /home/git/gitaly/config.toml +``` + +#### Check Gitaly configuration + +Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly +configuration file may contain syntax errors. The block name +`[[storages]]`, which may occur more than once in your `config.toml` +file, should be `[[storage]]` instead. + +```shell +sudo -u git -H sed -i.pre-9.5 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml +``` + +#### Compile Gitaly + +```shell +cd /home/git/gitaly +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) +sudo -u git -H make +``` + +### 10. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +If you use MySQL with replication, or just have MySQL configured with binary logging, +you will need to also run the following on all of your MySQL servers: + +```bash +mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;" +``` + +You can make this setting permanent by adding it to your `my.cnf`: + +``` +log_bin_trust_function_creators=1 +``` + +### 11. Update configuration files + +#### New configuration options for `gitlab.yml` + +There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +cd /home/git/gitlab + +git diff origin/9-4-stable:config/gitlab.yml.example origin/9-5-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +cd /home/git/gitlab + +# For HTTPS configurations +git diff origin/9-4-stable:lib/support/nginx/gitlab-ssl origin/9-5-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/9-4-stable:lib/support/nginx/gitlab origin/9-5-stable:lib/support/nginx/gitlab +``` + +If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx +configuration as GitLab application no longer handles setting it. + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`: + +```sh +cd /home/git/gitlab + +git diff origin/9-4-stable:lib/support/init.d/gitlab.default.example origin/9-5-stable:lib/support/init.d/gitlab.default.example +``` + +Ensure you're still up-to-date with the latest init script changes: + +```bash +cd /home/git/gitlab + +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +For Ubuntu 16.04.1 LTS: + +```bash +sudo systemctl daemon-reload +``` + +### 12. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 13. Start application + +```bash +sudo service gitlab start +sudo service nginx restart +``` + +### 14. Check application status + +Check if GitLab and its environment are configured correctly: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` + +To make sure you didn't miss anything run a more thorough check: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (9.4) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 9.3 to 9.4](9.3-to-9.4.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/config/gitlab.yml.example +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/lib/support/init.d/gitlab.default.example diff --git a/doc/update/README.md b/doc/update/README.md index 22dbc7c750f..c98e20686e0 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -34,17 +34,67 @@ update them are in [a separate document][omnidocker]. ## Upgrading without downtime -Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or patch version of GitLab -without having to take your GitLab instance offline. However, for this to work -there are the following requirements: - -1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3. -2. You have to be on the most recent patch release. For example, if 9.1.15 is the last - release of 9.1 then you can safely upgrade from that version to any 9.2.x version. - However, if you are running 9.1.14 you first need to upgrade to 9.1.15. +Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or +patch version of GitLab without having to take your GitLab instance offline. +However, for this to work there are the following requirements: + +1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to + 9.3. 2. You have to use [post-deployment migrations](../development/post_deployment_migrations.md). -3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required. +3. You are using PostgreSQL. If you are using MySQL please look at the release + post to see if downtime is required. + +Most of the time you can safely upgrade from a patch release to the next minor +release if the patch release is not the latest. For example, upgrading from +9.1.1 to 9.2.0 should be safe even if 9.1.2 has been released. We do recommend +you check the release posts of any releases between your current and target +version just in case they include any migrations that may require you to upgrade +1 release at a time. + +Some releases may also include so called "background migrations". These +migrations are performed in the background by Sidekiq and are often used for +migrating data. Background migrations are only added in the monthly releases. + +Certain major/minor releases may require a set of background migrations to be +finished. To guarantee this such a release will process any remaining jobs +before continuing the upgrading procedure. While this won't require downtime +(if the above conditions are met) we recommend users to keep at least 1 week +between upgrading major/minor releases, allowing the background migrations to +finish. The time necessary to complete these migrations can be reduced by +increasing the number of Sidekiq workers that can process jobs in the +`background_migration` queue. + +As a rule of thumb, any database smaller than 10 GB won't take too much time to +upgrade; perhaps an hour at most per minor release. Larger databases however may +require more time, but this is highly dependent on the size of the database and +the migrations that are being performed. + +### Examples + +To help explain this, let's look at some examples. + +**Example 1:** You are running a large GitLab installation using version 9.4.2, +which is the latest patch release of 9.4. When GitLab 9.5.0 is released this +installation can be safely upgraded to 9.5.0 without requiring downtime if the +requirements mentioned above are met. You can also skip 9.5.0 and upgrade to +9.5.1 once it's released, but you **can not** upgrade straight to 9.6.0; you +_have_ to first upgrade to a 9.5.x release. + +**Example 2:** You are running a large GitLab installation using version 9.4.2, +which is the latest patch release of 9.4. GitLab 9.5 includes some background +migrations, and 10.0 will require these to be completed (processing any +remaining jobs for you). Skipping 9.5 is not possible without downtime, and due +to the background migrations would require potentially hours of downtime +depending on how long it takes for the background migrations to complete. To +work around this you will have to upgrade to 9.5.x first, then wait at least a +week before upgrading to 10.0. + +**Example 3:** You use MySQL as the database for GitLab. Any upgrade to a new +major/minor release will require downtime. If a release includes any background +migrations this could potentially lead to hours of downtime, depending on the +size of your database. To work around this you will have to use PostgreSQL and +meet the other online upgrade requirements mentioned above. ## Upgrading between editions diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index ac1bcb8f241..12408123158 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -35,7 +35,7 @@ current version with `cat VERSION`). cd /home/git/gitlab sudo -u git -H git fetch --all -sudo -u git -H git checkout -- Gemfile.lock db/schema.rb +sudo -u git -H git checkout -- Gemfile.lock db/schema.rb locale sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG ``` @@ -56,11 +56,21 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +### 4. Compile GetText PO files + +Internationalization was added in `v9.2.0` so these commands are only +required for versions equal or major to it. + +```bash +sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production +sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production +``` + # Clean up assets and cache sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production ``` -### 4. Update gitlab-workhorse to the corresponding version +### 5. Update gitlab-workhorse to the corresponding version ```bash cd /home/git/gitlab @@ -68,7 +78,7 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production ``` -### 5. Update gitlab-shell to the corresponding version +### 6. Update gitlab-shell to the corresponding version ```bash cd /home/git/gitlab-shell @@ -78,14 +88,14 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi' ``` -### 6. Start application +### 7. Start application ```bash sudo service gitlab start sudo service nginx restart ``` -### 7. Check application status +### 8. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 2691cf7d671..08da721c71d 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -42,7 +42,7 @@ In GitLab, a namespace is a unique name to be used as a user name, a group name, For example, consider a user called John: -1. John creates his account on GitLab.com with the username `jonh`; +1. John creates his account on GitLab.com with the username `john`; his profile will be accessed under `https://gitlab.example.com/john` 1. John creates a group for his team with the groupname `john-team`; his group and its projects will be accessed under `https://gitlab.example.com/john-team` diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 0d29b471d52..b42b8f0a525 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -248,7 +248,7 @@ GFM will recognize the following: | `~123` | label by ID | | `~bug` | one-word label by name | | `~"feature request"` | multi-word label by name | -| `%123` | milestone by ID | +| `%123` | project milestone by ID | | `%v1.23` | one-word milestone by name | | `%"release candidate"` | multi-word milestone by name | | `9ba12248` | specific commit | @@ -262,7 +262,7 @@ GFM also recognizes certain cross-project references: |:----------------------------------------|:------------------------| | `namespace/project#123` | issue | | `namespace/project!123` | merge request | -| `namespace/project%123` | milestone | +| `namespace/project%123` | project milestone | | `namespace/project$123` | snippet | | `namespace/project@9ba12248` | specific commit | | `namespace/project@9ba12248...b19a04f5` | commit range comparison | @@ -274,7 +274,7 @@ It also has a shorthand version to reference other projects from the same namesp |:------------------------------|:------------------------| | `project#123` | issue | | `project!123` | merge request | -| `project%123` | milestone | +| `project%123` | project milestone | | `project$123` | snippet | | `project@9ba12248` | specific commit | | `project@9ba12248...b19a04f5` | commit range comparison | diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index c03a2df9a72..47eb0b34f66 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -438,7 +438,6 @@ X-Gitlab-Event: Note Hook "iid": 1, "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", "position": 0, - "locked_at": null, "source":{ "name":"Gitlab Test", "description":"Aut reprehenderit ut est.", diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 1848514e2dd..876b98a4dc5 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -27,6 +27,10 @@ of that milestone and the issues/merge requests count that it shares across the In addition to that you will be able to filter issues or merge requests by group milestones in all projects that belongs to the milestone group. +## Milestone promotion + +You will be able to promote a project milestone to a group milestone [in the future](https://gitlab.com/gitlab-org/gitlab-ce/issues/35833). + ## Special milestone filters In addition to the milestones that exist in the project or group, there are some @@ -49,3 +53,8 @@ is calculated as; closed and merged merge requests plus all closed issues divide total merge requests and issues. ![Milestone statistics](img/progress.png) + +## Quick actions + +[Quick actions](../quick_actions.md) are available for assigning and removing +project and group milestones. diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index 915d766dd60..f6559b6be2f 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -100,7 +100,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps group = owned_group %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| - project = create(:empty_project, path: path, group: group) + project = create(:project, path: path, group: group) milestone = create :milestone, title: "Version 7.2", project: project create(:label, project: project, title: 'bug') diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 6b288b47da4..a2d9a0332e0 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -15,7 +15,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'Group "Owned" has a public project "Public-project"' do group = owned_group - @project = create :empty_project, :public, + @project = create :project, :public, group: group, name: "Public-project" end @@ -109,7 +109,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'Group "Owned" has archived project' do group = Group.find_by(name: 'Owned') - @archived_project = create(:empty_project, :archived, namespace: group, path: "archived-project") + @archived_project = create(:project, :archived, namespace: group, path: "archived-project") end step 'I should see "archived" label' do diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index b58d595c7c6..b4403becb0d 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -47,11 +47,11 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'other projects have deploy keys' do - @second_project = create(:empty_project, namespace: create(:group)) + @second_project = create(:project, namespace: create(:group)) @second_project.team << [current_user, :master] create(:deploy_keys_project, project: @second_project) - @third_project = create(:empty_project, namespace: create(:group)) + @third_project = create(:project, namespace: create(:group)) @third_project.team << [current_user, :master] create(:deploy_keys_project, project: @third_project, deploy_key: @second_project.deploy_keys.first) end diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index c6cabace25b..420ac8a695a 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -30,8 +30,8 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps expect(@merge_request.source_project).to eq @forked_project expect(@merge_request.source_branch).to eq "fix" expect(@merge_request.target_branch).to eq "master" - expect(page).to have_content @forked_project.path_with_namespace - expect(page).to have_content @project.path_with_namespace + expect(page).to have_content @forked_project.full_path + expect(page).to have_content @project.full_path expect(page).to have_content @merge_request.source_branch expect(page).to have_content @merge_request.target_branch @@ -43,10 +43,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps expect(page).to have_content('Target branch') first('.js-source-project').click - first('.dropdown-source-project a', text: @forked_project.path_with_namespace) + first('.dropdown-source-project a', text: @forked_project.full_path) first('.js-target-project').click - first('.dropdown-target-project a', text: @project.path_with_namespace) + first('.dropdown-target-project a', text: @project.full_path) first('.js-source-branch').click wait_for_requests @@ -81,8 +81,8 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps expect(@merge_request.source_project).to eq @forked_project expect(@merge_request.source_branch).to eq "fix" expect(@merge_request.target_branch).to eq "master" - expect(page).to have_content @forked_project.path_with_namespace - expect(page).to have_content @project.path_with_namespace + expect(page).to have_content @forked_project.full_path + expect(page).to have_content @project.full_path expect(page).to have_content @merge_request.source_branch expect(page).to have_content @merge_request.target_branch end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 170e2f16c80..0a89c1baf20 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -10,7 +10,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I save project' do - click_button 'Save changes' + page.within '.general-settings' do + click_button 'Save changes' + end end step 'I should see project with new settings' do @@ -31,7 +33,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps :project_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') ) - click_button 'Save changes' + page.within '.general-settings' do + click_button 'Save changes' + end @project.reload end @@ -50,7 +54,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps :project_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') ) - click_button 'Save changes' + page.within '.general-settings' do + click_button 'Save changes' + end @project.reload end @@ -69,7 +75,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'change project default branch' do select 'fix', from: 'project_default_branch' - click_button 'Save changes' + page.within '.general-settings' do + click_button 'Save changes' + end end step 'I should see project default branch changed' do diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb index b2ceb8dd9a8..53a2463af53 100644 --- a/features/steps/project/redirects.rb +++ b/features/steps/project/redirects.rb @@ -4,11 +4,11 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps include SharedProject step 'public project "Community"' do - create(:empty_project, :public, name: 'Community') + create(:project, :public, name: 'Community') end step 'private project "Enterprise"' do - create(:empty_project, :private, name: 'Enterprise') + create(:project, :private, name: 'Enterprise') end step 'I visit project "Community" page' do @@ -47,7 +47,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I should be redirected to "Community" page' do project = Project.find_by(name: 'Community') - expect(current_path).to eq "/#{project.path_with_namespace}" + expect(current_path).to eq "/#{project.full_path}" expect(status_code).to eq 200 end @@ -61,7 +61,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I should be redirected to "Enterprise" page' do project = Project.find_by(name: 'Enterprise') - expect(current_path).to eq "/#{project.path_with_namespace}" + expect(current_path).to eq "/#{project.full_path}" expect(status_code).to eq 200 end end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index ff4c9deee2a..5c4025ace34 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -34,7 +34,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I own project "Website"' do - @project = create(:empty_project, name: "Website", namespace: @user.namespace) + @project = create(:project, name: "Website", namespace: @user.namespace) @project.team << [@user, :master] end @@ -68,7 +68,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps step 'I share project with group "OpenSource"' do project = Project.find_by(name: 'Shop') os_group = create(:group, name: 'OpenSource') - create(:empty_project, group: os_group) + create(:project, group: os_group) @os_user1 = create(:user) @os_user2 = create(:user) os_group.add_owner(@os_user1) diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 6a478c50e5e..855757e34b3 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -63,7 +63,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'That page has two revisions' do - @page.update("new content", message: "second commit") + @page.update(content: "new content", message: "second commit") end step 'I click the History button' do @@ -142,7 +142,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I should see non-escaped link in the pages list' do - expect(page).to have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three-test']") + expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']") end step 'I edit the Wiki page with a path' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index da1cdd9f897..00f7cded2ae 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -54,7 +54,7 @@ module SharedProject # Create an empty project without caring about the name step 'I own an empty project' do - @project = create(:empty_project, + @project = create(:project, name: 'Empty Project', namespace: @user.namespace) @project.team << [@user, :master] end @@ -103,7 +103,7 @@ module SharedProject step 'I should see project settings' do expect(current_path).to eq edit_project_path(@project) expect(page).to have_content("Project name") - expect(page).to have_content("Sharing & Permissions") + expect(page).to have_content("Sharing and permissions") end def current_project @@ -276,7 +276,7 @@ module SharedProject def user_owns_project(user_name:, project_name:, visibility: :private) user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore) project = Project.find_by(name: project_name) - project ||= create(:empty_project, visibility, name: project_name, namespace: user.namespace) + project ||= create(:project, visibility, name: project_name, namespace: user.namespace) project.team << [user, :master] end end diff --git a/features/steps/user.rb b/features/steps/user.rb index 271c9b097d4..59385a6ab59 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -38,6 +38,6 @@ class Spinach::Features::User < Spinach::FeatureSteps end def contributed_project - @contributed_project ||= create(:empty_project, :public) + @contributed_project ||= create(:project, :public) end end diff --git a/lib/api/api.rb b/lib/api/api.rb index 045a0db1842..94df543853b 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -48,8 +48,8 @@ module API end before { header['X-Frame-Options'] = 'SAMEORIGIN' } - before { Gitlab::I18n.locale = current_user&.preferred_language } + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } rescue_from Gitlab::Access::AccessDeniedError do @@ -95,6 +95,7 @@ module API mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages + mount ::API::CircuitBreakers mount ::API::Commits mount ::API::CommitStatuses mount ::API::DeployKeys @@ -123,6 +124,7 @@ module API mount ::API::ProjectHooks mount ::API::Projects mount ::API::ProjectSnippets + mount ::API::ProtectedBranches mount ::API::Repositories mount ::API::Runner mount ::API::Runners @@ -139,6 +141,7 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::GroupVariables mount ::API::Version route :any, '*path' do diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 9dd60d1833b..d3dbf941298 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -37,6 +37,7 @@ module API present branch, with: Entities::RepoBranch, project: user_project end + # Note: This API will be deprecated in favor of the protected branches API. # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}` # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility), # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. @@ -65,9 +66,9 @@ module API service_args = [user_project, current_user, protected_branch_params] protected_branch = if protected_branch - ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch) + ::ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch) else - ProtectedBranches::ApiCreateService.new(*service_args).execute + ::ProtectedBranches::ApiCreateService.new(*service_args).execute end if protected_branch.valid? @@ -77,6 +78,7 @@ module API end end + # Note: This API will be deprecated in favor of the protected branches API. desc 'Unprotect a single branch' do success Entities::RepoBranch end diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb new file mode 100644 index 00000000000..118883f5ea5 --- /dev/null +++ b/lib/api/circuit_breakers.rb @@ -0,0 +1,50 @@ +module API + class CircuitBreakers < Grape::API + before { authenticated_as_admin! } + + resource :circuit_breakers do + params do + requires :type, + type: String, + desc: "The type of circuitbreaker", + values: ['repository_storage'] + end + resource ':type' do + namespace '', requirements: { type: 'repository_storage' } do + helpers do + def failing_storage_health + @failing_storage_health ||= Gitlab::Git::Storage::Health.for_failing_storages + end + + def storage_health + @failing_storage_health ||= Gitlab::Git::Storage::Health.for_all_storages + end + end + + desc 'Get all failing git storages' do + detail 'This feature was introduced in GitLab 9.5' + success Entities::RepositoryStorageHealth + end + get do + present storage_health, with: Entities::RepositoryStorageHealth + end + + desc 'Get all failing git storages' do + detail 'This feature was introduced in GitLab 9.5' + success Entities::RepositoryStorageHealth + end + get 'failing' do + present failing_storage_health, with: Entities::RepositoryStorageHealth + end + + desc 'Reset all storage failures and open circuitbreaker' do + detail 'This feature was introduced in GitLab 9.5' + end + delete do + Gitlab::Git::Storage::CircuitBreaker.reset_all! + end + end + end + end + end +end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index bcb842b9211..ea78737288a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -4,13 +4,14 @@ module API class Commits < Grape::API include PaginationParams - before { authenticate! } + COMMIT_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(sha: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :download_code, user_project } params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a project repository commits' do success Entities::RepoCommit end @@ -21,7 +22,7 @@ module API optional :path, type: String, desc: 'The file path' use :pagination end - get ":id/repository/commits" do + get ':id/repository/commits' do path = params[:path] before = params[:until] after = params[:since] @@ -53,16 +54,19 @@ module API detail 'This feature was introduced in GitLab 8.13' end params do - requires :branch, type: String, desc: 'The name of branch' + requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.' requires :commit_message, type: String, desc: 'Commit message' requires :actions, type: Array[Hash], desc: 'Actions to perform in commit' + optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end - post ":id/repository/commits" do + post ':id/repository/commits' do authorize! :push_code, user_project - attrs = declared_params.merge(start_branch: declared_params[:branch], branch_name: declared_params[:branch]) + attrs = declared_params + attrs[:branch_name] = attrs.delete(:branch) + attrs[:start_branch] ||= attrs[:branch_name] result = ::Files::MultiService.new(user_project, current_user, attrs).execute @@ -76,42 +80,42 @@ module API desc 'Get a specific commit of a project' do success Entities::RepoCommitDetail - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ":id/repository/commits/:sha" do + get ':id/repository/commits/:sha', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit present commit, with: Entities::RepoCommitDetail end desc 'Get the diff for a specific commit of a project' do - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ":id/repository/commits/:sha/diff" do + get ':id/repository/commits/:sha/diff', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit commit.raw_diffs.to_a end desc "Get a commit's comments" do success Entities::CommitNote - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do use :pagination requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ':id/repository/commits/:sha/comments' do + get ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -125,10 +129,10 @@ module API success Entities::RepoCommit end params do - requires :sha, type: String, desc: 'A commit sha to be cherry picked' + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked' requires :branch, type: String, desc: 'The name of the branch' end - post ':id/repository/commits/:sha/cherry_pick' do + post ':id/repository/commits/:sha/cherry_pick', requirements: COMMIT_ENDPOINT_REQUIREMENTS do authorize! :push_code, user_project commit = user_project.commit(params[:sha]) @@ -157,7 +161,7 @@ module API success Entities::CommitNote end params do - requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA" + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment' requires :note, type: String, desc: 'The text of the comment' optional :path, type: String, desc: 'The file path' given :path do @@ -165,7 +169,7 @@ module API requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line' end end - post ':id/repository/commits/:sha/comments' do + post ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ce25be34ec4..3bb1910a441 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -66,13 +66,6 @@ module API expose :job_events end - class BasicProjectDetails < Grape::Entity - expose :id - expose :http_url_to_repo, :web_url - expose :name, :name_with_namespace - expose :path, :path_with_namespace - end - class SharedGroup < Grape::Entity expose :group_id expose :group_name do |group_link, options| @@ -81,7 +74,16 @@ module API expose :group_access, as: :group_access_level end - class Project < Grape::Entity + class BasicProjectDetails < Grape::Entity + expose :id, :description, :default_branch, :tag_list + expose :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :name, :name_with_namespace + expose :path, :path_with_namespace + expose :star_count, :forks_count + expose :created_at, :last_activity_at + end + + class Project < BasicProjectDetails include ::API::Helpers::RelatedResourcesHelpers expose :_links do @@ -114,12 +116,9 @@ module API end end - expose :id, :description, :default_branch, :tag_list expose :archived?, as: :archived - expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :visibility expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } - expose :name, :name_with_namespace - expose :path, :path_with_namespace expose :container_registry_enabled # Expose old field names with the new permissions methods to keep API compatible @@ -129,18 +128,16 @@ module API expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) } expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) } - expose :created_at, :last_activity_at expose :shared_runners_enabled expose :lfs_enabled?, as: :lfs_enabled expose :creator_id expose :namespace, using: 'API::Entities::Namespace' - expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } + expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? } expose :import_status expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } expose :avatar_url do |user, options| user.avatar_url(only_path: false) end - expose :star_count, :forks_count expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds, as: :public_jobs @@ -240,7 +237,7 @@ module API end expose :protected do |repo_branch, options| - ProtectedBranch.protected?(options[:project], repo_branch.name) + ::ProtectedBranch.protected?(options[:project], repo_branch.name) end expose :developers_can_push do |repo_branch, options| @@ -299,6 +296,19 @@ module API expose :deleted_file?, as: :deleted_file end + class ProtectedRefAccess < Grape::Entity + expose :access_level + expose :access_level_description do |protected_ref_access| + protected_ref_access.humanize + end + end + + class ProtectedBranch < Grape::Entity + expose :name + expose :push_access_levels, using: Entities::ProtectedRefAccess + expose :merge_access_levels, using: Entities::ProtectedRefAccess + end + class Milestone < Grape::Entity expose :id, :iid expose :project_id, if: -> (entity, options) { entity&.project_id } @@ -444,6 +454,9 @@ module API end class Note < Grape::Entity + # Only Issue and MergeRequest have iid + NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze + expose :id expose :note, as: :body expose :attachment_identifier, as: :attachment @@ -451,6 +464,9 @@ module API expose :created_at, :updated_at expose :system?, as: :system expose :noteable_id, :noteable_type + + # Avoid N+1 queries as much as possible + expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) } end class AwardEmoji < Grape::Entity @@ -483,7 +499,7 @@ module API class Event < Grape::Entity expose :title, :project_id, :action_name - expose :target_id, :target_type, :author_id + expose :target_id, :target_iid, :target_type, :author_id expose :data, :target_title expose :created_at expose :note, using: Entities::Note, if: ->(event, options) { event.note? } @@ -689,7 +705,7 @@ module API class RepoTag < Grape::Entity expose :name, :message - expose :commit do |repo_tag, options| + expose :commit, using: Entities::RepoCommit do |repo_tag, options| options[:project].repository.commit(repo_tag.dereferenced_target) end @@ -941,5 +957,11 @@ module API expose :ip_address expose :submitted, as: :akismet_submitted end + + class RepositoryStorageHealth < Grape::Entity + expose :storage_name + expose :failing_on_hosts + expose :total_failures + end end end diff --git a/lib/api/files.rb b/lib/api/files.rb index 521287ee2b4..450334fee84 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -4,7 +4,7 @@ module API def commit_params(attrs) { file_path: attrs[:file_path], - start_branch: attrs[:branch], + start_branch: attrs[:start_branch] || attrs[:branch], branch_name: attrs[:branch], commit_message: attrs[:commit_message], file_content: attrs[:content], @@ -37,8 +37,9 @@ module API params :simple_file_params do requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :branch, type: String, desc: 'The name of branch' - requires :commit_message, type: String, desc: 'Commit Message' + requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.' + requires :commit_message, type: String, desc: 'Commit message' + optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'The email of the author' optional :author_name, type: String, desc: 'The name of the author' end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb new file mode 100644 index 00000000000..f64da4ab77b --- /dev/null +++ b/lib/api/group_variables.rb @@ -0,0 +1,96 @@ +module API + class GroupVariables < Grape::API + include PaginationParams + + before { authenticate! } + before { authorize! :admin_build, user_group } + + params do + requires :id, type: String, desc: 'The ID of a group' + end + + resource :groups, requirements: { id: %r{[^/]+} } do + desc 'Get group-level variables' do + success Entities::Variable + end + params do + use :pagination + end + get ':id/variables' do + variables = user_group.variables + present paginate(variables), with: Entities::Variable + end + + desc 'Get a specific variable from a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + get ':id/variables/:key' do + key = params[:key] + variable = user_group.variables.find_by(key: key) + + return not_found!('GroupVariable') unless variable + + present variable, with: Entities::Variable + end + + desc 'Create a new variable in a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + optional :protected, type: String, desc: 'Whether the variable is protected' + end + post ':id/variables' do + variable_params = declared_params(include_missing: false) + + variable = user_group.variables.create(variable_params) + + if variable.valid? + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Update an existing variable from a group' do + success Entities::Variable + end + params do + optional :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + optional :protected, type: String, desc: 'Whether the variable is protected' + end + put ':id/variables/:key' do + variable = user_group.variables.find_by(key: params[:key]) + + return not_found!('GroupVariable') unless variable + + variable_params = declared_params(include_missing: false).except(:key) + + if variable.update(variable_params) + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Delete an existing variable from a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + delete ':id/variables/:key' do + variable = user_group.variables.find_by(key: params[:key]) + not_found!('GroupVariable') unless variable + + status 204 + variable.destroy + end + end + end +end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 234825480f2..99b8b62691f 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -16,6 +16,8 @@ module API @current_user = initial_current_user + Gitlab::I18n.locale = @current_user&.preferred_language + sudo! @current_user diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index d9cae1501f8..a50ea0b52aa 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -1,8 +1,10 @@ +# rubocop:disable GitlabSecurity/PublicSend + module API module Helpers module MembersHelpers def find_source(source_type, id) - public_send("find_#{source_type}!", id) + public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend end def authorize_admin_source!(source_type, source) diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 65ff89edf65..4e4e473994b 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -139,7 +139,7 @@ module API helpers do def find_project_noteable(noteables_str, noteable_id) - public_send("find_project_#{noteables_str.singularize}", noteable_id) + public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend end def noteable_read_ability_name(noteable) diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb new file mode 100644 index 00000000000..d742f2e18d0 --- /dev/null +++ b/lib/api/protected_branches.rb @@ -0,0 +1,85 @@ +module API + class ProtectedBranches < Grape::API + include PaginationParams + + BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX) + + before { authorize_admin_project } + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc "Get a project's protected branches" do + success Entities::ProtectedBranch + end + params do + use :pagination + end + get ':id/protected_branches' do + protected_branches = user_project.protected_branches.preload(:push_access_levels, :merge_access_levels) + + present paginate(protected_branches), with: Entities::ProtectedBranch, project: user_project + end + + desc 'Get a single protected branch' do + success Entities::ProtectedBranch + end + params do + requires :name, type: String, desc: 'The name of the branch or wildcard' + end + get ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + protected_branch = user_project.protected_branches.find_by!(name: params[:name]) + + present protected_branch, with: Entities::ProtectedBranch, project: user_project + end + + desc 'Protect a single branch or wildcard' do + success Entities::ProtectedBranch + end + params do + requires :name, type: String, desc: 'The name of the protected branch' + optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER, + values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS, + desc: 'Access levels allowed to push (defaults: `40`, master access level)' + optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER, + values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS, + desc: 'Access levels allowed to merge (defaults: `40`, master access level)' + end + post ':id/protected_branches' do + protected_branch = user_project.protected_branches.find_by(name: params[:name]) + if protected_branch + conflict!("Protected branch '#{params[:name]}' already exists") + end + + protected_branch_params = { + name: params[:name], + push_access_levels_attributes: [{ access_level: params[:push_access_level] }], + merge_access_levels_attributes: [{ access_level: params[:merge_access_level] }] + } + + service_args = [user_project, current_user, protected_branch_params] + + protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute + + if protected_branch.persisted? + present protected_branch, with: Entities::ProtectedBranch, project: user_project + else + render_api_error!(protected_branch.errors.full_messages, 422) + end + end + + desc 'Unprotect a single branch' + params do + requires :name, type: String, desc: 'The name of the protected branch' + end + delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + protected_branch = user_project.protected_branches.find_by!(name: params[:name]) + + protected_branch.destroy + + status 204 + end + end + end +end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 405d25ca3c1..88fc62d33df 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -90,7 +90,7 @@ module API if result.valid? if result.build Gitlab::Metrics.add_event(:build_found, - project: result.build.project.path_with_namespace) + project: result.build.project.full_path) present result.build, with: Entities::JobRequest::Response else Gitlab::Metrics.add_event(:build_not_found) @@ -119,7 +119,7 @@ module API job.trace.set(params[:trace]) if params[:trace] Gitlab::Metrics.add_event(:update_build, - project: job.project.path_with_namespace) + project: job.project.full_path) case params[:state].to_s when 'success' diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 633a858f8c7..1333747cced 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -2,19 +2,21 @@ module API class Tags < Grape::API include PaginationParams + TAG_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(tag_name: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :download_code, user_project } params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a project repository tags' do success Entities::RepoTag end params do use :pagination end - get ":id/repository/tags" do + get ':id/repository/tags' do tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse) present paginate(tags), with: Entities::RepoTag, project: user_project end @@ -25,7 +27,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag' end - get ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do + get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do tag = user_project.repository.find_tag(params[:tag_name]) not_found!('Tag') unless tag @@ -60,7 +62,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag' end - delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do + delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = ::Tags::DestroyService.new(user_project, current_user) @@ -78,7 +80,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag' requires :description, type: String, desc: 'Release notes with markdown support' end - post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do + post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = CreateReleaseService.new(user_project, current_user) @@ -98,7 +100,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag' requires :description, type: String, desc: 'Release notes with markdown support' end - put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do + put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = UpdateReleaseService.new(user_project, current_user) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index d1f7e364029..55191169dd4 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -59,10 +59,10 @@ module API requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end post ':id/mark_as_done' do - todo = current_user.todos.find(params[:id]) - TodoService.new.mark_todos_as_done([todo], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) + todo = Todo.find(params[:id]) - present todo.reload, with: Entities::Todo, current_user: current_user + present todo, with: Entities::Todo, current_user: current_user end desc 'Mark all todos as done' diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index 773f667abe0..4a2e9c9cbb0 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -68,7 +68,7 @@ module API expose :lfs_enabled?, as: :lfs_enabled expose :creator_id expose :namespace, using: 'API::Entities::Namespace' - expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } + expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? } expose :avatar_url do |user, options| user.avatar_url(only_path: false) end diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb index e3b311d61cd..2f2cf259987 100644 --- a/lib/api/v3/todos.rb +++ b/lib/api/v3/todos.rb @@ -11,10 +11,10 @@ module API requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end delete ':id' do - todo = current_user.todos.find(params[:id]) - TodoService.new.mark_todos_as_done([todo], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) + todo = Todo.find(params[:id]) - present todo.reload, with: ::API::Entities::Todo, current_user: current_user + present todo, with: ::API::Entities::Todo, current_user: current_user end desc 'Mark all todos as done' diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index ca6d6848d41..b9a573d3542 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -198,11 +198,11 @@ module Backup end def archives_to_backup - ARCHIVES_TO_BACKUP.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact + ARCHIVES_TO_BACKUP.map { |name| (name + ".tar.gz") unless skipped?(name) }.compact end def folders_to_backup - FOLDERS_TO_BACKUP.reject{ |name| skipped?(name) } + FOLDERS_TO_BACKUP.reject { |name| skipped?(name) } end def disabled_features diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 02ed1e49ef8..88821ae56e0 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -7,7 +7,7 @@ module Backup prepare Project.find_each(batch_size: 1000) do |project| - progress.print " * #{project.path_with_namespace} ... " + progress.print " * #{project.full_path} ... " path_to_project_repo = path_to_repo(project) path_to_project_bundle = path_to_bundle(project) @@ -42,7 +42,7 @@ module Backup path_to_wiki_bundle = path_to_bundle(wiki) if File.exist?(path_to_wiki_repo) - progress.print " * #{wiki.path_with_namespace} ... " + progress.print " * #{wiki.full_path} ... " if empty_repo?(wiki) progress.puts " [SKIPPED]".color(:cyan) else @@ -71,11 +71,11 @@ module Backup end Project.find_each(batch_size: 1000) do |project| - progress.print " * #{project.path_with_namespace} ... " + progress.print " * #{project.full_path} ... " path_to_project_repo = path_to_repo(project) path_to_project_bundle = path_to_bundle(project) - project.ensure_dir_exist + project.ensure_storage_path_exist cmd = if File.exist?(path_to_project_bundle) %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_project_bundle} #{path_to_project_repo}) @@ -104,7 +104,7 @@ module Backup path_to_wiki_bundle = path_to_bundle(wiki) if File.exist?(path_to_wiki_bundle) - progress.print " * #{wiki.path_with_namespace} ... " + progress.print " * #{wiki.full_path} ... " # If a wiki bundle exists, first remove the empty repo # that was initialized with ProjectWiki.new() and then @@ -142,11 +142,11 @@ module Backup end def path_to_bundle(project) - File.join(backup_repos_path, project.path_with_namespace + '.bundle') + File.join(backup_repos_path, project.disk_path + '.bundle') end def path_to_tars(project, dir = nil) - path = File.join(backup_repos_path, project.path_with_namespace) + path = File.join(backup_repos_path, project.disk_path) if dir File.join(path, "#{dir}.tar") @@ -185,14 +185,14 @@ module Backup def progress_warn(project, cmd, output) progress.puts "[WARNING] Executing #{cmd}".color(:orange) - progress.puts "Ignoring error on #{project.path_with_namespace} - #{output}".color(:orange) + progress.puts "Ignoring error on #{project.full_path} - #{output}".color(:orange) end def empty_repo?(project_or_wiki) project_or_wiki.repository.expire_exists_cache # protect backups from stale cache project_or_wiki.repository.empty_repo? rescue => e - progress.puts "Ignoring repository error and continuing backing up project: #{project_or_wiki.path_with_namespace} - #{e.message}".color(:orange) + progress.puts "Ignoring repository error and continuing backing up project: #{project_or_wiki.full_path} - #{e.message}".color(:orange) false end diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 7a262dd025c..ef4578aabd6 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -54,42 +54,42 @@ module Banzai self.class.references_in(*args, &block) end + # Implement in child class + # Example: project.merge_requests.find def find_object(project, id) - # Implement in child class - # Example: project.merge_requests.find end - def find_object_cached(project, id) - if RequestStore.active? - cache = find_objects_cache[object_class][project.id] + # Override if the link reference pattern produces a different ID (global + # ID vs internal ID, for instance) to the regular reference pattern. + def find_object_from_link(project, id) + find_object(project, id) + end - get_or_set_cache(cache, id) { find_object(project, id) } - else + # Implement in child class + # Example: project_merge_request_url + def url_for_object(object, project) + end + + def find_object_cached(project, id) + cached_call(:banzai_find_object, id, path: [object_class, project.id]) do find_object(project, id) end end - def project_from_ref_cached(ref) - if RequestStore.active? - cache = project_refs_cache - - get_or_set_cache(cache, ref) { project_from_ref(ref) } - else - project_from_ref(ref) + def find_object_from_link_cached(project, id) + cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do + find_object_from_link(project, id) end end - def url_for_object(object, project) - # Implement in child class - # Example: project_merge_request_url + def project_from_ref_cached(ref) + cached_call(:banzai_project_refs, ref) do + project_from_ref(ref) + end end def url_for_object_cached(object, project) - if RequestStore.active? - cache = url_for_object_cache[object_class][project.id] - - get_or_set_cache(cache, object) { url_for_object(object, project) } - else + cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do url_for_object(object, project) end end @@ -120,7 +120,7 @@ module Banzai if link == inner_html && inner_html =~ /\A#{link_pattern}/ replace_link_node_with_text(node, link) do - object_link_filter(inner_html, link_pattern) + object_link_filter(inner_html, link_pattern, link_reference: true) end next @@ -128,7 +128,7 @@ module Banzai if link =~ /\A#{link_pattern}\z/ replace_link_node_with_href(node, link) do - object_link_filter(link, link_pattern, link_content: inner_html) + object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true) end next @@ -146,15 +146,26 @@ module Banzai # text - String text to replace references in. # pattern - Reference pattern to match against. # link_content - Original content of the link being replaced. + # link_reference - True if this was using the link reference pattern, + # false otherwise. # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text, pattern, link_content: nil) + def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| project_path = full_project_path(namespace_ref, project_ref) project = project_from_ref_cached(project_path) - if project && object = find_object_cached(project, id) + if project + object = + if link_reference + find_object_from_link_cached(project, id) + else + find_object_cached(project, id) + end + end + + if object title = object_link_title(object) klass = reference_class(object_sym) @@ -259,7 +270,7 @@ module Banzai found = [] projects.each do |project| - ref = project.path_with_namespace + ref = project.full_path get_or_set_cache(cache, ref) { project } found << ref end @@ -277,7 +288,7 @@ module Banzai end def current_project_path - @current_project_path ||= project.path_with_namespace + @current_project_path ||= project.full_path end def current_project_namespace_path @@ -297,15 +308,17 @@ module Banzai RequestStore[:banzai_project_refs] ||= {} end - def find_objects_cache - RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } - end - end + def cached_call(request_store_key, cache_key, path: []) + if RequestStore.active? + cache = RequestStore[request_store_key] ||= Hash.new do |hash, key| + hash[key] = Hash.new { |h, k| h[k] = {} } + end - def url_for_object_cache - RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } + cache = cache.dig(*path) if path.any? + + get_or_set_cache(cache, cache_key) { yield } + else + yield end end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 45c033d32a8..4fc5f211e84 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -8,8 +8,15 @@ module Banzai Milestone end + # Links to project milestones contain the IID, but when we're handling + # 'regular' references, we need to use the global ID to disambiguate + # between group and project milestones. def find_object(project, id) - project.milestones.find_by(iid: id) + find_milestone_with_finder(project, id: id) + end + + def find_object_from_link(project, iid) + find_milestone_with_finder(project, iid: iid) end def references_in(text, pattern = Milestone.reference_pattern) @@ -22,7 +29,7 @@ module Banzai milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~[:namespace], $~ + yield match, milestone.id, $~[:project], $~[:namespace], $~ else match end @@ -36,7 +43,8 @@ module Banzai return unless project milestone_params = milestone_params(milestone_id, milestone_name) - project.milestones.find_by(milestone_params) + + find_milestone_with_finder(project, milestone_params) end def milestone_params(iid, name) @@ -47,15 +55,27 @@ module Banzai end end + def find_milestone_with_finder(project, params) + finder_params = { project_ids: [project.id], order: nil } + + # We don't support IID lookups for group milestones, because IIDs can + # clash between group and project milestones. + if project.group && !params[:iid] + finder_params[:group_ids] = [project.group.id] + end + + MilestonesFinder.new(finder_params).execute.find_by(params) + end + def url_for_object(milestone, project) - h = Gitlab::Routing.url_helpers - h.project_milestone_url(project, milestone, - only_path: context[:only_path]) + Gitlab::Routing + .url_helpers + .milestone_url(milestone, only_path: context[:only_path]) end def object_link_text(object, matches) milestone_link = escape_once(super) - reference = object.project.to_reference(project) + reference = object.project&.to_reference(project) if reference.present? "#{milestone_link} <i>in #{reference}</i>".html_safe diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index c2fed57a0d8..758f15c8a67 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -51,7 +51,7 @@ module Banzai uri.path = [ relative_url_root, - context[:project].path_with_namespace, + context[:project].full_path, uri_type(file_path), Addressable::URI.escape(ref), Addressable::URI.escape(file_path) diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb index 45bb66dc99f..09844931be5 100644 --- a/lib/banzai/filter/upload_link_filter.rb +++ b/lib/banzai/filter/upload_link_filter.rb @@ -28,7 +28,7 @@ module Banzai end def build_url(uri) - File.join(Gitlab.config.gitlab.url, project.path_with_namespace, uri) + File.join(Gitlab.config.gitlab.url, project.full_path, uri) end def project diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index c7801cb5baf..ad08c0905e2 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -132,6 +132,8 @@ module Banzai end def self.cacheless_render(text, context = {}) + return text.to_s unless text.present? + Gitlab::Metrics.measure(:banzai_cacheless_render) do result = render_result(text, context) diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index 55402101e43..8354fc8d595 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -254,7 +254,7 @@ module Ci def state state = STATE_PARAMS.inject({}) do |h, param| - h[param] = send(param) + h[param] = send(param) # rubocop:disable GitlabSecurity/PublicSend h end Base64.urlsafe_encode64(state.to_json) @@ -266,7 +266,7 @@ module Ci return if state[:offset].to_i > stream.size STATE_PARAMS.each do |param| - send("#{param}=".to_sym, state[param]) + send("#{param}=".to_sym, state[param]) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index e2e91ce99cd..79058c02ce5 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -29,7 +29,7 @@ module Ci if result.valid? if result.build Gitlab::Metrics.add_event(:build_found, - project: result.build.project.path_with_namespace) + project: result.build.project.full_path) present result.build, with: Entities::BuildDetails else @@ -64,7 +64,7 @@ module Ci build.trace.set(params[:trace]) if params[:trace] Gitlab::Metrics.add_event(:update_build, - project: build.project.path_with_namespace) + project: build.project.full_path) case params[:state].to_s when 'success' diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb index 872e418c788..76a69bf8a83 100644 --- a/lib/ci/charts.rb +++ b/lib/ci/charts.rb @@ -47,7 +47,7 @@ module Ci def collect query = project.pipelines - .where("? > #{Ci::Pipeline.table_name}.created_at AND #{Ci::Pipeline.table_name}.created_at > ?", @to, @from) + .where("? > #{Ci::Pipeline.table_name}.created_at AND #{Ci::Pipeline.table_name}.created_at > ?", @to, @from) # rubocop:disable GitlabSecurity/SqlInjection totals_count = grouped_count(query) success_count = grouped_count(query.success) diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb index 4c0aee6c48f..fd7b97d3167 100644 --- a/lib/constraints/project_url_constrainer.rb +++ b/lib/constraints/project_url_constrainer.rb @@ -6,6 +6,8 @@ class ProjectUrlConstrainer return false unless DynamicPathValidator.valid_project_path?(full_path) + # We intentionally allow SELECT(*) here so result of this query can be used + # as cache for further Project.find_by_full_path calls within request Project.find_by_full_path(full_path, follow_redirects: request.get?).present? end end diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index b1eb1a6cef1..ae65653645b 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -28,7 +28,13 @@ module DeclarativePolicy subject = find_delegate(subject) - class_for_class(subject.class) + policy_class = class_for_class(subject.class) + raise "no policy for #{subject.class.name}" if policy_class.nil? + policy_class + end + + def has_policy?(subject) + !class_for_class(subject.class).nil? end private @@ -51,9 +57,7 @@ module DeclarativePolicy end end - policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR) - raise "no policy for #{subject.class.name}" if policy_class.nil? - policy_class + subject_class.instance_variable_get(CLASS_CACHE_IVAR) end def compute_class_for_class(subject_class) @@ -71,6 +75,8 @@ module DeclarativePolicy nil end end + + nil end def find_delegate(subject) diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb index b5c615da4e3..56afd1f1392 100644 --- a/lib/declarative_policy/runner.rb +++ b/lib/declarative_policy/runner.rb @@ -76,6 +76,8 @@ module DeclarativePolicy @state = State.new steps_by_score do |step, score| + return if !debug && @state.prevented? + passed = nil case step.action when :enable then @@ -93,10 +95,7 @@ module DeclarativePolicy # been prevented. unless @state.prevented? passed = step.pass? - if passed - @state.prevent! - return unless debug - end + @state.prevent! if passed end debug << inspect_step(step, score, passed) if debug @@ -141,13 +140,14 @@ module DeclarativePolicy end steps = Set.new(@steps) + remaining_enablers = steps.count { |s| s.enable? } loop do return if steps.empty? # if the permission hasn't yet been enabled and we only have # prevent steps left, we short-circuit the state here - @state.prevent! if !@state.enabled? && steps.all?(&:prevent?) + @state.prevent! if !@state.enabled? && remaining_enablers == 0 lowest_score = Float::INFINITY next_step = nil @@ -162,6 +162,8 @@ module DeclarativePolicy steps.delete(next_step) + remaining_enablers -= 1 if next_step.enable? + yield next_step, lowest_score end end diff --git a/lib/github/client.rb b/lib/github/client.rb index e65d908d232..9c476df7d46 100644 --- a/lib/github/client.rb +++ b/lib/github/client.rb @@ -1,13 +1,16 @@ module Github class Client + TIMEOUT = 60 + attr_reader :connection, :rate_limit def initialize(options) - @connection = Faraday.new(url: options.fetch(:url)) do |faraday| - faraday.options.open_timeout = options.fetch(:timeout, 60) - faraday.options.timeout = options.fetch(:timeout, 60) + @connection = Faraday.new(url: options.fetch(:url, root_endpoint)) do |faraday| + faraday.options.open_timeout = options.fetch(:timeout, TIMEOUT) + faraday.options.timeout = options.fetch(:timeout, TIMEOUT) faraday.authorization 'token', options.fetch(:token) faraday.adapter :net_http + faraday.ssl.verify = verify_ssl end @rate_limit = RateLimit.new(connection) @@ -19,5 +22,32 @@ module Github Github::Response.new(connection.get(url, query)) end + + private + + def root_endpoint + custom_endpoint || github_endpoint + end + + def custom_endpoint + github_omniauth_provider.dig('args', 'client_options', 'site') + end + + def verify_ssl + # If there is no config, we're connecting to github.com + # and we should verify ssl. + github_omniauth_provider.fetch('verify_ssl', true) + end + + def github_endpoint + OmniAuth::Strategies::GitHub.default_options[:client_options][:site] + end + + def github_omniauth_provider + @github_omniauth_provider ||= + Gitlab.config.omniauth.providers + .find { |provider| provider.name == 'github' } + .to_h + end end end diff --git a/lib/github/import.rb b/lib/github/import.rb index ff5d7db2705..4cc01593ef4 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -41,13 +41,16 @@ module Github self.reset_callbacks :validate end - attr_reader :project, :repository, :repo, :options, :errors, :cached, :verbose + attr_reader :project, :repository, :repo, :repo_url, :wiki_url, + :options, :errors, :cached, :verbose - def initialize(project, options) + def initialize(project, options = {}) @project = project @repository = project.repository @repo = project.import_source - @options = options + @repo_url = project.import_url + @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git') + @options = options.reverse_merge(token: project.import_data&.credentials&.fetch(:user)) @verbose = options.fetch(:verbose, false) @cached = Hash.new { |hash, key| hash[key] = Hash.new } @errors = [] @@ -65,6 +68,8 @@ module Github fetch_pull_requests puts 'Fetching issues...'.color(:aqua) if verbose fetch_issues + puts 'Fetching releases...'.color(:aqua) if verbose + fetch_releases puts 'Cloning wiki repository...'.color(:aqua) if verbose fetch_wiki_repository puts 'Expiring repository cache...'.color(:aqua) if verbose @@ -72,6 +77,7 @@ module Github true rescue Github::RepositoryFetchError + expire_repository_cache false ensure keep_track_of_errors @@ -81,23 +87,21 @@ module Github def fetch_repository begin - project.create_repository unless project.repository.exists? - project.repository.add_remote('github', "https://#{options.fetch(:token)}@github.com/#{repo}.git") + project.ensure_repository + project.repository.add_remote('github', repo_url) project.repository.set_remote_as_mirror('github') project.repository.fetch_remote('github', forced: true) - rescue Gitlab::Shell::Error => e - error(:project, "https://github.com/#{repo}.git", e.message) + rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e + error(:project, repo_url, e.message) raise Github::RepositoryFetchError end end def fetch_wiki_repository - wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git" - wiki_path = "#{project.path_with_namespace}.wiki" + return if project.wiki.repository_exists? - unless project.wiki.repository_exists? - gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url) - end + wiki_path = "#{project.disk_path}.wiki" + gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url) rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, # this means that repo has wiki enabled, but have no pages. So, @@ -309,7 +313,7 @@ module Github next unless representation.valid? release = ::Release.find_or_initialize_by(project_id: project.id, tag: representation.tag) - next unless relese.new_record? + next unless release.new_record? begin release.description = representation.description @@ -337,7 +341,7 @@ module Github def user_id(user, fallback_id = nil) return unless user.present? - return cached[:user_ids][user.id] if cached[:user_ids].key?(user.id) + return cached[:user_ids][user.id] if cached[:user_ids][user.id].present? gitlab_user_id = user_id_by_external_uid(user.id) || user_id_by_email(user.email) @@ -367,7 +371,7 @@ module Github end def expire_repository_cache - repository.expire_content_cache + repository.expire_content_cache if project.repository_exists? end def keep_track_of_errors diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 9bed81e7327..7d3aa532750 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -218,7 +218,8 @@ module Gitlab def full_authentication_abilities read_authentication_abilities + [ :push_code, - :create_container_image + :create_container_image, + :admin_container_image ] end alias_method :api_scope_authentication_abilities, :full_authentication_abilities diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb new file mode 100644 index 00000000000..0fbc6b70989 --- /dev/null +++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb @@ -0,0 +1,107 @@ +module Gitlab + module BackgroundMigration + class DeserializeMergeRequestDiffsAndCommits + attr_reader :diff_ids, :commit_rows, :file_rows + + class MergeRequestDiff < ActiveRecord::Base + self.table_name = 'merge_request_diffs' + end + + BUFFER_ROWS = 1000 + + def perform(start_id, stop_id) + merge_request_diffs = MergeRequestDiff + .select(:id, :st_commits, :st_diffs) + .where('st_commits IS NOT NULL OR st_diffs IS NOT NULL') + .where(id: start_id..stop_id) + + reset_buffers! + + merge_request_diffs.each do |merge_request_diff| + commits, files = single_diff_rows(merge_request_diff) + + diff_ids << merge_request_diff.id + commit_rows.concat(commits) + file_rows.concat(files) + + if diff_ids.length > BUFFER_ROWS || + commit_rows.length > BUFFER_ROWS || + file_rows.length > BUFFER_ROWS + + flush_buffers! + end + end + + flush_buffers! + end + + private + + def reset_buffers! + @diff_ids = [] + @commit_rows = [] + @file_rows = [] + end + + def flush_buffers! + if diff_ids.any? + MergeRequestDiff.transaction do + Gitlab::Database.bulk_insert('merge_request_diff_commits', commit_rows) + Gitlab::Database.bulk_insert('merge_request_diff_files', file_rows) + + MergeRequestDiff.where(id: diff_ids).update_all(st_commits: nil, st_diffs: nil) + end + end + + reset_buffers! + end + + def single_diff_rows(merge_request_diff) + sha_attribute = Gitlab::Database::ShaAttribute.new + commits = YAML.load(merge_request_diff.st_commits) rescue [] + + commit_rows = commits.map.with_index do |commit, index| + commit_hash = commit.to_hash.with_indifferent_access.except(:parent_ids) + sha = commit_hash.delete(:id) + + commit_hash.merge( + merge_request_diff_id: merge_request_diff.id, + relative_order: index, + sha: sha_attribute.type_cast_for_database(sha) + ) + end + + diffs = YAML.load(merge_request_diff.st_diffs) rescue [] + diffs = [] unless valid_raw_diffs?(diffs) + + file_rows = diffs.map.with_index do |diff, index| + diff_hash = diff.to_hash.with_indifferent_access.merge( + binary: false, + merge_request_diff_id: merge_request_diff.id, + relative_order: index + ) + + # Compatibility with old diffs created with Psych. + diff_hash.tap do |hash| + diff_text = hash[:diff] + + if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only? + hash[:binary] = true + hash[:diff] = [diff_text].pack('m0') + end + end + end + + [commit_rows, file_rows] + end + + # Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as + # valid, because we don't render them usefully anyway. + def valid_raw_diffs?(diffs) + return false unless diffs.respond_to?(:each) + + diffs.all? { |diff| diff.is_a?(Hash) } + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 5a6d9ae99a0..28bbf3b384e 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -61,7 +61,7 @@ module Gitlab def import_wiki return if project.wiki.repository_exists? - path_with_namespace = "#{project.path_with_namespace}.wiki" + path_with_namespace = "#{project.full_path}.wiki" import_url = project.import_url.sub(/\.git\z/, ".git/wiki") gitlab_shell.import_repository(project.repository_storage_path, path_with_namespace, import_url) rescue StandardError => e diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 1611eba31da..d671867e7c7 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -77,8 +77,8 @@ EOM def initialize(merge_request, project) @merge_request = merge_request - @our_commit = merge_request.source_branch_head.raw.raw_commit - @their_commit = merge_request.target_branch_head.raw.raw_commit + @our_commit = merge_request.source_branch_head.raw.rugged_commit + @their_commit = merge_request.target_branch_head.raw.rugged_commit @project = project end end diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index bf557103cfd..0735243e021 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -48,7 +48,7 @@ module Gitlab end def starting_month - Date.today.month + Date.current.month end private @@ -66,12 +66,18 @@ module Gitlab .select(:id) conditions = t[:created_at].gteq(date_from.beginning_of_day) - .and(t[:created_at].lteq(Date.today.end_of_day)) + .and(t[:created_at].lteq(Date.current.end_of_day)) .and(t[:author_id].eq(contributor.id)) + date_interval = if Gitlab::Database.postgresql? + "INTERVAL '#{Time.zone.now.utc_offset} seconds'" + else + "INTERVAL #{Time.zone.now.utc_offset} SECOND" + end + Event.reorder(nil) - .select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount') - .group(t[:project_id], t[:target_type], t[:action], 'date(created_at)') + .select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount') + .group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})") .where(conditions) .having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql))) end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index b260822788d..2479b4a7706 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -54,7 +54,7 @@ module Gitlab end def serialize_commit(event, commit, query) - commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project) + commit = Commit.from_hash(commit.to_hash, @project) AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit) end diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb new file mode 100644 index 00000000000..dfd17e35707 --- /dev/null +++ b/lib/gitlab/daemon.rb @@ -0,0 +1,62 @@ +module Gitlab + class Daemon + def self.initialize_instance(*args) + raise "#{name} singleton instance already initialized" if @instance + @instance = new(*args) + Kernel.at_exit(&@instance.method(:stop)) + @instance + end + + def self.instance + @instance ||= initialize_instance + end + + attr_reader :thread + + def thread? + !thread.nil? + end + + def initialize + @mutex = Mutex.new + end + + def enabled? + true + end + + def start + return unless enabled? + + @mutex.synchronize do + return thread if thread? + + @thread = Thread.new { start_working } + end + end + + def stop + @mutex.synchronize do + return unless thread? + + stop_working + + if thread + thread.wakeup if thread.alive? + thread.join + @thread = nil + end + end + end + + private + + def start_working + raise NotImplementedError + end + + def stop_working + # no-ops + end + end +end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index d2863a4da71..6d7de52cb80 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -79,13 +79,6 @@ module Gitlab @new_content_sha = refs&.head_sha end - def new_content_commit - return @new_content_commit if defined?(@new_content_commit) - - sha = new_content_commit - @new_content_commit = repository.commit(sha) if sha - end - def old_content_sha return if new_file? return @old_content_sha if defined?(@old_content_sha) @@ -94,13 +87,6 @@ module Gitlab @old_content_sha = refs&.base_sha end - def old_content_commit - return @old_content_commit if defined?(@old_content_commit) - - sha = old_content_sha - @old_content_commit = repository.commit(sha) if sha - end - def new_blob return @new_blob if defined?(@new_blob) @@ -123,10 +109,6 @@ module Gitlab new_content_sha || old_content_sha end - def content_commit - new_content_commit || old_content_commit - end - def blob new_blob || old_blob end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 2d89ccfc354..0603141e441 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -21,7 +21,7 @@ module Gitlab def to_hash hash = {} - serialize_keys.each { |key| hash[key] = send(key) } + serialize_keys.each { |key| hash[key] = send(key) } # rubocop:disable GitlabSecurity/PublicSend hash end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index 85e6db0a689..abd401224d8 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -98,10 +98,11 @@ module Gitlab if status.zero? @ee_branch_found = ee_branch_prefix - else - _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}]) + return end + _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}]) + if status.zero? @ee_branch_found = ee_branch_suffix else @@ -181,8 +182,6 @@ module Gitlab end def find_merge_base_with_master(branch:) - return if merge_base_found? - # Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403) # In total we go (20 + 54 + 148 + 403 = 625) commits deeper depth = 20 diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 31579e94a87..8eea33b9ab5 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -15,7 +15,6 @@ module Gitlab def execute raise SentNotificationNotFoundError unless sent_notification - raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/ validate_permission!(:create_note) diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index dd1d9dcd555..cd9d3a6483f 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -117,7 +117,7 @@ module Gitlab def subject subject_text = '[Git]' - subject_text << "[#{project.path_with_namespace}]" + subject_text << "[#{project.full_path}]" subject_text << "[#{ref_name}]" if @action == :push subject_text << ' ' diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 0d6b08b5d29..c8f4591d060 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -26,6 +26,9 @@ module Gitlab raise EmptyEmailError if @raw.blank? mail = build_mail + + ignore_auto_submitted!(mail) + mail_key = extract_mail_key(mail) handler = Handler.for(mail, mail_key) @@ -87,6 +90,16 @@ module Gitlab break key if key end end + + def ignore_auto_submitted!(mail) + # Mail::Header#[] is case-insensitive + auto_submitted = mail.header['Auto-Submitted']&.value + + # Mail::Field#value would strip leading and trailing whitespace + raise AutoGeneratedEmailError if + # See also https://tools.ietf.org/html/rfc3834 + auto_submitted && auto_submitted != 'no' + end end end end diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index 781f9c56a42..8ddc91e341d 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -11,7 +11,7 @@ module Gitlab # obscure encoding with low confidence. # There is a lot more info with this merge request: # https://gitlab.com/gitlab-org/gitlab_git/merge_requests/77#note_4754193 - ENCODING_CONFIDENCE_THRESHOLD = 40 + ENCODING_CONFIDENCE_THRESHOLD = 50 def encode!(message) return nil unless message.respond_to? :force_encoding diff --git a/lib/gitlab/environment.rb b/lib/gitlab/environment.rb new file mode 100644 index 00000000000..5e0dd6e7859 --- /dev/null +++ b/lib/gitlab/environment.rb @@ -0,0 +1,7 @@ +module Gitlab + module Environment + def self.hostname + @hostname ||= ENV['HOSTNAME'] || Socket.gethostname + end + end +end diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb index 0deaab01b5b..31effdba292 100644 --- a/lib/gitlab/git/blame.rb +++ b/lib/gitlab/git/blame.rb @@ -1,5 +1,3 @@ -# Gitaly note: JV: needs 1 RPC for #load_blame. - module Gitlab module Git class Blame @@ -18,7 +16,7 @@ module Gitlab def each @blames.each do |blame| yield( - Gitlab::Git::Commit.new(blame.commit), + Gitlab::Git::Commit.new(@repo, blame.commit), blame.line ) end @@ -26,15 +24,29 @@ module Gitlab private - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/376 def load_blame - cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path}) - # Read in binary mode to ensure ASCII-8BIT - raw_output = IO.popen(cmd, 'rb') {|io| io.read } + raw_output = @repo.gitaly_migrate(:blame) do |is_enabled| + if is_enabled + load_blame_by_gitaly + else + load_blame_by_shelling_out + end + end + output = encode_utf8(raw_output) process_raw_blame output end + def load_blame_by_gitaly + @repo.gitaly_commit_client.raw_blame(@sha, @path) + end + + def load_blame_by_shelling_out + cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path}) + # Read in binary mode to ensure ASCII-8BIT + IO.popen(cmd, 'rb') {|io| io.read } + end + def process_raw_blame(output) lines, final = [], [] info, commits = {}, {} diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index db6cfc9671f..77b81d2d437 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -20,66 +20,7 @@ module Gitlab if is_enabled find_by_gitaly(repository, sha, path) else - find_by_rugged(repository, sha, path) - end - end - end - - def find_by_gitaly(repository, sha, path) - path = path.sub(/\A\/*/, '') - path = '/' if path.empty? - name = File.basename(path) - entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) - return unless entry - - case entry.type - when :COMMIT - new( - id: entry.oid, - name: name, - size: 0, - data: '', - path: path, - commit_id: sha - ) - when :BLOB - new( - id: entry.oid, - name: name, - size: entry.size, - data: entry.data.dup, - mode: entry.mode.to_s(8), - path: path, - commit_id: sha, - binary: binary?(entry.data) - ) - end - end - - def find_by_rugged(repository, sha, path) - commit = repository.lookup(sha) - root_tree = commit.tree - - blob_entry = find_entry_by_path(repository, root_tree.oid, path) - - return nil unless blob_entry - - if blob_entry[:type] == :commit - submodule_blob(blob_entry, path, sha) - else - blob = repository.lookup(blob_entry[:oid]) - - if blob - new( - id: blob.oid, - name: blob_entry[:name], - size: blob.size, - data: blob.content(MAX_DATA_DISPLAY_SIZE), - mode: blob_entry[:filemode].to_s(8), - path: path, - commit_id: sha, - binary: blob.binary? - ) + find_by_rugged(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE) end end end @@ -109,6 +50,21 @@ module Gitlab detect && detect[:type] == :binary end + # Returns an array of Blob instances, specified in blob_references as + # [[commit_sha, path], [commit_sha, path], ...]. If blob_size_limit < 0 then the + # full blob contents are returned. If blob_size_limit >= 0 then each blob will + # contain no more than limit bytes in its data attribute. + # + # Keep in mind that this method may allocate a lot of memory. It is up + # to the caller to limit the number of blobs and blob_size_limit. + # + def batch(repository, blob_references, blob_size_limit: nil) + blob_size_limit ||= MAX_DATA_DISPLAY_SIZE + blob_references.map do |sha, path| + find_by_rugged(repository, sha, path, limit: blob_size_limit) + end + end + private # Recursive search of blob id by path @@ -153,6 +109,66 @@ module Gitlab commit_id: sha ) end + + def find_by_gitaly(repository, sha, path) + path = path.sub(/\A\/*/, '') + path = '/' if path.empty? + name = File.basename(path) + entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) + return unless entry + + case entry.type + when :COMMIT + new( + id: entry.oid, + name: name, + size: 0, + data: '', + path: path, + commit_id: sha + ) + when :BLOB + new( + id: entry.oid, + name: name, + size: entry.size, + data: entry.data.dup, + mode: entry.mode.to_s(8), + path: path, + commit_id: sha, + binary: binary?(entry.data) + ) + end + end + + def find_by_rugged(repository, sha, path, limit:) + commit = repository.lookup(sha) + root_tree = commit.tree + + blob_entry = find_entry_by_path(repository, root_tree.oid, path) + + return nil unless blob_entry + + if blob_entry[:type] == :commit + submodule_blob(blob_entry, path, sha) + else + blob = repository.lookup(blob_entry[:oid]) + + if blob + new( + id: blob.oid, + name: blob_entry[:name], + size: blob.size, + # Rugged::Blob#content is expensive; don't call it if we don't have to. + data: limit.zero? ? '' : blob.content(limit), + mode: blob_entry[:filemode].to_s(8), + path: path, + commit_id: sha, + binary: blob.binary? + ) + end + end + end end def initialize(options) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index ca7e3a7c4be..fd4dfdb09a2 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -14,7 +14,7 @@ module Gitlab attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator - delegate :tree, to: :raw_commit + delegate :tree, to: :rugged_commit def ==(other) return false unless other.is_a?(Gitlab::Git::Commit) @@ -50,19 +50,29 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321 def find(repo, commit_id = "HEAD") + # Already a commit? return commit_id if commit_id.is_a?(Gitlab::Git::Commit) - return decorate(commit_id) if commit_id.is_a?(Rugged::Commit) - obj = if commit_id.is_a?(String) - repo.rev_parse_target(commit_id) - else - Gitlab::Git::Ref.dereference_object(commit_id) - end + # A rugged reference? + commit_id = Gitlab::Git::Ref.dereference_object(commit_id) + return decorate(repo, commit_id) if commit_id.is_a?(Rugged::Commit) - return nil unless obj.is_a?(Rugged::Commit) + # Some weird thing? + return nil unless commit_id.is_a?(String) - decorate(obj) - rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository + commit = repo.gitaly_migrate(:find_commit) do |is_enabled| + if is_enabled + repo.gitaly_commit_client.find_commit(commit_id) + else + obj = repo.rev_parse_target(commit_id) + + obj.is_a?(Rugged::Commit) ? obj : nil + end + end + + decorate(repo, commit) if commit + rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, + Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository nil end @@ -102,7 +112,7 @@ module Gitlab if is_enabled repo.gitaly_commit_client.between(base, head) else - repo.commits_between(base, head).map { |c| decorate(c) } + repo.rugged_commits_between(base, head).map { |c| decorate(repo, c) } end end rescue Rugged::ReferenceError @@ -169,7 +179,7 @@ module Gitlab offset = actual_options[:skip] limit = actual_options[:max_count] walker.each(offset: offset, limit: limit) do |commit| - commits.push(decorate(commit)) + commits.push(decorate(repo, commit)) end walker.reset @@ -183,27 +193,8 @@ module Gitlab Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options) end - def decorate(commit, ref = nil) - Gitlab::Git::Commit.new(commit, ref) - end - - # Returns a diff object for the changes introduced by +rugged_commit+. - # If +rugged_commit+ doesn't have a parent, then the diff is between - # this commit and an empty repo. See Repository#diff for the keys - # allowed in the +options+ hash. - def diff_from_parent(rugged_commit, options = {}) - options ||= {} - break_rewrites = options[:break_rewrites] - actual_options = Gitlab::Git::Diff.filter_diff_options(options) - - diff = if rugged_commit.parents.empty? - rugged_commit.diff(actual_options.merge(reverse: true)) - else - rugged_commit.parents[0].diff(rugged_commit, actual_options) - end - - diff.find_similar!(break_rewrites: break_rewrites) - diff + def decorate(repository, commit, ref = nil) + Gitlab::Git::Commit.new(repository, commit, ref) end # Returns the `Rugged` sorting type constant for one or more given @@ -221,7 +212,7 @@ module Gitlab end end - def initialize(raw_commit, head = nil) + def initialize(repository, raw_commit, head = nil) raise "Nil as raw commit passed" unless raw_commit case raw_commit @@ -229,12 +220,13 @@ module Gitlab init_from_hash(raw_commit) when Rugged::Commit init_from_rugged(raw_commit) - when Gitlab::GitalyClient::Commit + when Gitaly::GitCommit init_from_gitaly(raw_commit) else raise "Invalid raw commit type: #{raw_commit.class}" end + @repository = repository @head = head end @@ -269,19 +261,50 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324 def to_diff - diff_from_parent.patch + rugged_diff_from_parent.patch end # Returns a diff object for the changes from this commit's first parent. # If there is no parent, then the diff is between this commit and an - # empty repo. See Repository#diff for keys allowed in the +options+ + # empty repo. See Repository#diff for keys allowed in the +options+ # hash. def diff_from_parent(options = {}) - Commit.diff_from_parent(raw_commit, options) + Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled| + if is_enabled + @repository.gitaly_commit_client.diff_from_parent(self, options) + else + rugged_diff_from_parent(options) + end + end + end + + def rugged_diff_from_parent(options = {}) + options ||= {} + break_rewrites = options[:break_rewrites] + actual_options = Gitlab::Git::Diff.filter_diff_options(options) + + diff = if rugged_commit.parents.empty? + rugged_commit.diff(actual_options.merge(reverse: true)) + else + rugged_commit.parents[0].diff(rugged_commit, actual_options) + end + + diff.find_similar!(break_rewrites: break_rewrites) + diff end def deltas - @deltas ||= diff_from_parent.each_delta.map { |d| Gitlab::Git::Diff.new(d) } + @deltas ||= begin + deltas = Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled| + if is_enabled + @repository.gitaly_commit_client.commit_deltas(self) + else + rugged_diff_from_parent.each_delta + end + end + + deltas.map { |delta| Gitlab::Git::Diff.new(delta) } + end end def has_zero_stats? @@ -296,7 +319,7 @@ module Gitlab def to_hash serialize_keys.map.with_object({}) do |key, hash| - hash[key] = send(key) + hash[key] = send(key) # rubocop:disable GitlabSecurity/PublicSend end end @@ -309,14 +332,7 @@ module Gitlab end def parents - case raw_commit - when Rugged::Commit - raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) } - when Gitlab::GitalyClient::Commit - parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact - else - raise NotImplementedError, "commit source doesn't support #parents" - end + parent_ids.map { |oid| self.class.find(@repository, oid) }.compact end # Get the gpg signature of this commit. @@ -334,7 +350,7 @@ module Gitlab def to_patch(options = {}) begin - raw_commit.to_mbox(options) + rugged_commit.to_mbox(options) rescue Rugged::InvalidError => ex if ex.message =~ /commit \w+ is a merge commit/i 'Patch format is not currently supported for merge commits.' @@ -382,13 +398,21 @@ module Gitlab encode! @committer_email end + def rugged_commit + @rugged_commit ||= if raw_commit.is_a?(Rugged::Commit) + raw_commit + else + @repository.rev_parse_target(id) + end + end + private def init_from_hash(hash) raw_commit = hash.symbolize_keys serialize_keys.each do |key| - send("#{key}=", raw_commit[key]) + send("#{key}=", raw_commit[key]) # rubocop:disable GitlabSecurity/PublicSend end end @@ -415,10 +439,10 @@ module Gitlab # subject from the message to make it clearer when there's one # available but not the other. @message = (commit.body.presence || commit.subject).dup - @authored_date = Time.at(commit.author.date.seconds) + @authored_date = Time.at(commit.author.date.seconds).utc @author_name = commit.author.name.dup @author_email = commit.author.email.dup - @committed_date = Time.at(commit.committer.date.seconds) + @committed_date = Time.at(commit.committer.date.seconds).utc @committer_name = commit.committer.name.dup @committer_email = commit.committer.email.dup @parent_ids = commit.parent_ids diff --git a/lib/gitlab/git/commit_stats.rb b/lib/gitlab/git/commit_stats.rb index 57c29ad112c..00acb4763e9 100644 --- a/lib/gitlab/git/commit_stats.rb +++ b/lib/gitlab/git/commit_stats.rb @@ -16,7 +16,7 @@ module Gitlab @deletions = 0 @total = 0 - diff = commit.diff_from_parent + diff = commit.rugged_diff_from_parent diff.each_patch do |p| # TODO: Use the new Rugged convenience methods when they're released diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index 9e00abefd02..ce3d65062e8 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -143,7 +143,7 @@ module Gitlab hash = {} SERIALIZE_KEYS.each do |key| - hash[key] = send(key) + hash[key] = send(key) # rubocop:disable GitlabSecurity/PublicSend end hash @@ -221,7 +221,7 @@ module Gitlab raw_diff = hash.symbolize_keys SERIALIZE_KEYS.each do |key| - send(:"#{key}=", raw_diff[key.to_sym]) + send(:"#{key}=", raw_diff[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb index 87ed9c3ea26..6a601561c2a 100644 --- a/lib/gitlab/git/diff_collection.rb +++ b/lib/gitlab/git/diff_collection.rb @@ -28,7 +28,6 @@ module Gitlab @limits = self.class.collection_limits(options) @enforce_limits = !!options.fetch(:limits, true) @expanded = !!options.fetch(:expanded, true) - @from_gitaly = options.fetch(:from_gitaly, false) @line_count = 0 @byte_count = 0 @@ -44,7 +43,7 @@ module Gitlab return if @iterator.nil? Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled| - if is_enabled && @from_gitaly + if is_enabled && @iterator.is_a?(Gitlab::GitalyClient::DiffStitcher) each_gitaly_patch(&block) else each_rugged_patch(&block) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index a3bc79109f8..371f8797ff2 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -58,17 +58,18 @@ module Gitlab end end - # Alias to old method for compatibility - def raw - rugged - end - def rugged - @rugged ||= Rugged::Repository.new(path, alternates: alternate_object_directories) + @rugged ||= circuit_breaker.perform do + Rugged::Repository.new(path, alternates: alternate_object_directories) + end rescue Rugged::RepositoryError, Rugged::OSError raise NoRepository.new('no repository for such path') end + def circuit_breaker + @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage) + end + # Returns an Array of branch names # sorted by name ASC def branch_names @@ -281,7 +282,14 @@ module Gitlab # Return repo size in megabytes def size - size = popen(%w(du -sk), path).first.strip.to_i + size = gitaly_migrate(:repository_size) do |is_enabled| + if is_enabled + size_by_gitaly + else + size_by_shelling_out + end + end + (size.to_f / 1024).round(2) end @@ -296,21 +304,34 @@ module Gitlab # after: Time.new(2016, 4, 21, 14, 32, 10) # ) # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/446 def log(options) - raw_log(options).map { |c| Commit.decorate(c) } - end + default_options = { + limit: 10, + offset: 0, + path: nil, + follow: false, + skip_merges: false, + disable_walk: false, + after: nil, + before: nil + } - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/382 - def count_commits(options) - cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] - cmd << "--after=#{options[:after].iso8601}" if options[:after] - cmd << "--before=#{options[:before].iso8601}" if options[:before] - cmd += %W[--count #{options[:ref]}] - cmd += %W[-- #{options[:path]}] if options[:path].present? + options = default_options.merge(options) + options[:limit] ||= 0 + options[:offset] ||= 0 - raw_output = IO.popen(cmd) { |io| io.read } + raw_log(options).map { |c| Commit.decorate(self, c) } + end - raw_output.to_i + def count_commits(options) + gitaly_migrate(:count_commits) do |is_enabled| + if is_enabled + count_commits_by_gitaly(options) + else + count_commits_by_shelling_out(options) + end + end end def sha_from_ref(ref) @@ -327,7 +348,9 @@ module Gitlab # Return a collection of Rugged::Commits between the two revspec arguments. # See http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for # a detailed list of valid arguments. - def commits_between(from, to) + # + # Gitaly note: JV: to be deprecated in favor of Commit.between + def rugged_commits_between(from, to) walker = Rugged::Walker.new(rugged) walker.sorting(Rugged::SORT_NONE | Rugged::SORT_REVERSE) @@ -353,6 +376,13 @@ module Gitlab rugged.merge_base(from, to) end + # Gitaly note: JV: check gitlab-ee before removing this method. + def rugged_is_ancestor?(ancestor_id, descendant_id) + return false if ancestor_id.nil? || descendant_id.nil? + + merge_base_commit(ancestor_id, descendant_id) == ancestor_id + end + # Returns true is +from+ is direct ancestor to +to+, otherwise false def is_ancestor?(from, to) gitaly_commit_client.is_ancestor(from, to) @@ -636,6 +666,33 @@ module Gitlab @attributes.attributes(path) end + def languages(ref = nil) + Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled| + if is_enabled + gitaly_commit_client.languages(ref) + else + ref ||= rugged.head.target_id + languages = Linguist::Repository.new(rugged, ref).languages + total = languages.map(&:last).sum + + languages = languages.map do |language| + name, share = language + color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" + { + value: (share.to_f * 100 / total).round(2), + label: name, + color: color, + highlight: color + } + end + + languages.sort do |x, y| + y[:value] <=> x[:value] + end + end + end + end + def gitaly_repository Gitlab::GitalyClient::Util.repository(@storage, @relative_path) end @@ -652,6 +709,14 @@ module Gitlab @gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self) end + def gitaly_migrate(method, &block) + Gitlab::GitalyClient.migrate(method, &block) + rescue GRPC::NotFound => e + raise NoRepository.new(e) + rescue GRPC::BadStatus => e + raise CommandError.new(e) + end + private # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. @@ -669,20 +734,6 @@ module Gitlab end def raw_log(options) - default_options = { - limit: 10, - offset: 0, - path: nil, - follow: false, - skip_merges: false, - disable_walk: false, - after: nil, - before: nil - } - - options = default_options.merge(options) - options[:limit] ||= 0 - options[:offset] ||= 0 actual_ref = options[:ref] || root_ref begin sha = sha_from_ref(actual_ref) @@ -818,46 +869,6 @@ module Gitlab submodule_data.select { |path, data| data['id'] } end - # Returns true if +commit+ introduced changes to +path+, using commit - # trees to make that determination. Uses the history simplification - # rules that `git log` uses by default, where a commit is omitted if it - # is TREESAME to any parent. - # - # If the +follow+ option is true and the file specified by +path+ was - # renamed, then the path value is set to the old path. - def commit_touches_path?(commit, path, follow, walker) - entry = tree_entry(commit, path) - - if commit.parents.empty? - # This is the root commit, return true if it has +path+ in its tree - return !entry.nil? - end - - num_treesame = 0 - commit.parents.each do |parent| - parent_entry = tree_entry(parent, path) - - # Only follow the first TREESAME parent for merge commits - if num_treesame > 0 - walker.hide(parent) - next - end - - if entry.nil? && parent_entry.nil? - num_treesame += 1 - elsif entry && parent_entry && entry[:oid] == parent_entry[:oid] - num_treesame += 1 - end - end - - case num_treesame - when 0 - detect_rename(commit, commit.parents.first, path) if follow - true - else false - end - end - # Find the entry for +path+ in the tree for +commit+ def tree_entry(commit, path) pathname = Pathname.new(path) @@ -885,43 +896,6 @@ module Gitlab tmp_entry end - # Compare +commit+ and +parent+ for +path+. If +path+ is a file and was - # renamed in +commit+, then set +path+ to the old filename. - def detect_rename(commit, parent, path) - diff = parent.diff(commit, paths: [path], disable_pathspec_match: true) - - # If +path+ is a filename, not a directory, then we should only have - # one delta. We don't need to follow renames for directories. - return nil if diff.each_delta.count > 1 - - delta = diff.each_delta.first - if delta.added? - full_diff = parent.diff(commit) - full_diff.find_similar! - - full_diff.each_delta do |full_delta| - if full_delta.renamed? && path == full_delta.new_file[:path] - # Look for the old path in ancestors - path.replace(full_delta.old_file[:path]) - end - end - end - end - - # Returns true if the index entry has the special file mode that denotes - # a submodule. - def submodule?(index_entry) - index_entry[:mode] == 57344 - end - - # Return a Rugged::Index that has read from the tree at +ref_name+ - def populated_index(ref_name) - commit = rev_parse_target(ref_name) - index = rugged.index - index.read_tree(commit.tree) - index - end - # Return the Rugged patches for the diff between +from+ and +to+. def diff_patches(from, to, options = {}, *paths) options ||= {} @@ -967,16 +941,37 @@ module Gitlab end.sort_by(&:name) end + def last_commit_for_path_by_rugged(sha, path) + sha = last_commit_id_for_path(sha, path) + commit(sha) + end + def tags_from_gitaly gitaly_ref_client.tags end - def gitaly_migrate(method, &block) - Gitlab::GitalyClient.migrate(method, &block) - rescue GRPC::NotFound => e - raise NoRepository.new(e) - rescue GRPC::BadStatus => e - raise CommandError.new(e) + def size_by_shelling_out + popen(%w(du -sk), path).first.strip.to_i + end + + def size_by_gitaly + gitaly_repository_client.repository_size + end + + def count_commits_by_gitaly(options) + gitaly_commit_client.commit_count(options[:ref], options) + end + + def count_commits_by_shelling_out(options) + cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] + cmd << "--after=#{options[:after].iso8601}" if options[:after] + cmd << "--before=#{options[:before].iso8601}" if options[:before] + cmd += %W[--count #{options[:ref]}] + cmd += %W[-- #{options[:path]}] if options[:path].present? + + raw_output = IO.popen(cmd) { |io| io.read } + + raw_output.to_i end end end diff --git a/lib/gitlab/git/storage.rb b/lib/gitlab/git/storage.rb new file mode 100644 index 00000000000..e28be4b8a38 --- /dev/null +++ b/lib/gitlab/git/storage.rb @@ -0,0 +1,22 @@ +module Gitlab + module Git + module Storage + class Inaccessible < StandardError + attr_reader :retry_after + + def initialize(message = nil, retry_after = nil) + super(message) + @retry_after = retry_after + end + end + + CircuitOpen = Class.new(Inaccessible) + + REDIS_KEY_PREFIX = 'storage_accessible:'.freeze + + def self.redis + Gitlab::Redis::SharedState + end + end + end +end diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb new file mode 100644 index 00000000000..9ea9367d4b7 --- /dev/null +++ b/lib/gitlab/git/storage/circuit_breaker.rb @@ -0,0 +1,144 @@ +module Gitlab + module Git + module Storage + class CircuitBreaker + FailureInfo = Struct.new(:last_failure, :failure_count) + + attr_reader :storage, + :hostname, + :storage_path, + :failure_count_threshold, + :failure_wait_time, + :failure_reset_time, + :storage_timeout + + delegate :last_failure, :failure_count, to: :failure_info + + def self.reset_all! + pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*" + + Gitlab::Git::Storage.redis.with do |redis| + all_storage_keys = redis.keys(pattern) + redis.del(*all_storage_keys) unless all_storage_keys.empty? + end + + RequestStore.delete(:circuitbreaker_cache) + end + + def self.for_storage(storage) + cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do + Hash.new do |hash, storage_name| + hash[storage_name] = new(storage_name) + end + end + + cached_circuitbreakers[storage] + end + + def initialize(storage, hostname = Gitlab::Environment.hostname) + @storage = storage + @hostname = hostname + + config = Gitlab.config.repositories.storages[@storage] + @storage_path = config['path'] + @failure_count_threshold = config['failure_count_threshold'] + @failure_wait_time = config['failure_wait_time'] + @failure_reset_time = config['failure_reset_time'] + @storage_timeout = config['storage_timeout'] + end + + def perform + return yield unless Feature.enabled?('git_storage_circuit_breaker') + + check_storage_accessible! + + yield + end + + def circuit_broken? + return false if no_failures? + + recent_failure = last_failure > failure_wait_time.seconds.ago + too_many_failures = failure_count > failure_count_threshold + + recent_failure || too_many_failures + end + + # Memoizing the `storage_available` call means we only do it once per + # request when the storage is available. + # + # When the storage appears not available, and the memoized value is `false` + # we might want to try again. + def storage_available? + return @storage_available if @storage_available + + if @storage_available = Gitlab::Git::Storage::ForkedStorageCheck + .storage_available?(storage_path, storage_timeout) + track_storage_accessible + else + track_storage_inaccessible + end + + @storage_available + end + + def check_storage_accessible! + if circuit_broken? + raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_wait_time) + end + + unless storage_available? + raise Gitlab::Git::Storage::Inaccessible.new("#{storage} not accessible", failure_wait_time) + end + end + + def no_failures? + last_failure.blank? && failure_count == 0 + end + + def track_storage_inaccessible + @failure_info = FailureInfo.new(Time.now, failure_count + 1) + + Gitlab::Git::Storage.redis.with do |redis| + redis.pipelined do + redis.hset(cache_key, :last_failure, last_failure.to_i) + redis.hincrby(cache_key, :failure_count, 1) + redis.expire(cache_key, failure_reset_time) + end + end + end + + def track_storage_accessible + return if no_failures? + + @failure_info = FailureInfo.new(nil, 0) + + Gitlab::Git::Storage.redis.with do |redis| + redis.pipelined do + redis.hset(cache_key, :last_failure, nil) + redis.hset(cache_key, :failure_count, 0) + end + end + end + + def failure_info + @failure_info ||= get_failure_info + end + + def get_failure_info + last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis| + redis.hmget(cache_key, :last_failure, :failure_count) + end + + last_failure = Time.at(last_failure.to_i) if last_failure.present? + + FailureInfo.new(last_failure, failure_count.to_i) + end + + def cache_key + @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}" + end + end + end + end +end diff --git a/lib/gitlab/git/storage/forked_storage_check.rb b/lib/gitlab/git/storage/forked_storage_check.rb new file mode 100644 index 00000000000..91d8241f17b --- /dev/null +++ b/lib/gitlab/git/storage/forked_storage_check.rb @@ -0,0 +1,55 @@ +module Gitlab + module Git + module Storage + module ForkedStorageCheck + extend self + + def storage_available?(path, timeout_seconds = 5) + status = timeout_check(path, timeout_seconds) + + status.success? + end + + def timeout_check(path, timeout_seconds) + filesystem_check_pid = check_filesystem_in_process(path) + + deadline = timeout_seconds.seconds.from_now.utc + wait_time = 0.01 + status = nil + + while status.nil? + if deadline > Time.now.utc + sleep(wait_time) + _pid, status = Process.wait2(filesystem_check_pid, Process::WNOHANG) + else + Process.kill('KILL', filesystem_check_pid) + # Blocking wait, so we are sure the process is gone before continuing + _pid, status = Process.wait2(filesystem_check_pid) + end + end + + status + end + + # This will spawn a new 2 processes to do the check: + # The outer child (waiter) will spawn another child process (stater). + # + # The stater is the process is performing the actual filesystem check + # the check might hang if the filesystem is acting up. + # In this case we will send a `KILL` to the waiter, which will still + # be responsive while the stater is hanging. + def check_filesystem_in_process(path) + spawn('ruby', '-e', ruby_check, path, [:out, :err] => '/dev/null') + end + + def ruby_check + <<~RUBY_FILESYSTEM_CHECK + inner_pid = fork { File.stat(ARGV.first) } + Process.waitpid(inner_pid) + exit $?.exitstatus + RUBY_FILESYSTEM_CHECK + end + end + end + end +end diff --git a/lib/gitlab/git/storage/health.rb b/lib/gitlab/git/storage/health.rb new file mode 100644 index 00000000000..2d723147f4f --- /dev/null +++ b/lib/gitlab/git/storage/health.rb @@ -0,0 +1,91 @@ +module Gitlab + module Git + module Storage + class Health + attr_reader :storage_name, :info + + def self.pattern_for_storage(storage_name) + "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*" + end + + def self.for_all_storages + storage_names = Gitlab.config.repositories.storages.keys + results_per_storage = nil + + Gitlab::Git::Storage.redis.with do |redis| + keys_per_storage = all_keys_for_storages(storage_names, redis) + results_per_storage = load_for_keys(keys_per_storage, redis) + end + + results_per_storage.map do |name, info| + info.each { |i| i[:failure_count] = i[:failure_count].value.to_i } + new(name, info) + end + end + + def self.all_keys_for_storages(storage_names, redis) + keys_per_storage = {} + + redis.pipelined do + storage_names.each do |storage_name| + pattern = pattern_for_storage(storage_name) + + keys_per_storage[storage_name] = redis.keys(pattern) + end + end + + keys_per_storage + end + + def self.load_for_keys(keys_per_storage, redis) + info_for_keys = {} + + redis.pipelined do + keys_per_storage.each do |storage_name, keys_future| + info_for_storage = keys_future.value.map do |key| + { name: key, failure_count: redis.hget(key, :failure_count) } + end + + info_for_keys[storage_name] = info_for_storage + end + end + + info_for_keys + end + + def self.for_failing_storages + for_all_storages.select(&:failing?) + end + + def initialize(storage_name, info) + @storage_name = storage_name + @info = info + end + + def failing_info + @failing_info ||= info.select { |info_for_host| info_for_host[:failure_count] > 0 } + end + + def failing? + failing_info.any? + end + + def failing_on_hosts + @failing_on_hosts ||= failing_info.map do |info_for_host| + info_for_host[:name].split(':').last + end + end + + def failing_circuit_breakers + @failing_circuit_breakers ||= failing_on_hosts.map do |hostname| + CircuitBreaker.new(storage_name, hostname) + end + end + + def total_failures + @total_failures ||= failing_info.sum { |info_for_host| info_for_host[:failure_count] } + end + end + end + end +end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index c90ef282fdd..70177cd0fec 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -100,5 +100,9 @@ module Gitlab path = Rails.root.join(SERVER_VERSION_FILE) path.read.chomp end + + def self.encode(s) + s.dup.force_encoding(Encoding::ASCII_8BIT) + end end end diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb deleted file mode 100644 index 61fe462d762..00000000000 --- a/lib/gitlab/gitaly_client/commit.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Gitlab - module GitalyClient - class Commit - attr_reader :repository, :gitaly_commit - - delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit - - def initialize(repository, gitaly_commit) - @repository = repository - @gitaly_commit = gitaly_commit - end - end - end -end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index c6e52b530b3..692d7e02eef 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -29,22 +29,21 @@ module Gitlab request = Gitaly::CommitDiffRequest.new(request_params) response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request) - Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options.merge(from_gitaly: true)) + GitalyClient::DiffStitcher.new(response) end def commit_deltas(commit) request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit)) response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request) - response.flat_map do |msg| - msg.deltas.map { |d| Gitlab::Git::Diff.new(d) } - end + + response.flat_map { |msg| msg.deltas } end def tree_entry(ref, path, limit = nil) request = Gitaly::TreeEntryRequest.new( repository: @gitaly_repo, revision: ref, - path: path.dup.force_encoding(Encoding::ASCII_8BIT), + path: GitalyClient.encode(path), limit: limit.to_i ) @@ -85,15 +84,31 @@ module Gitlab end end - def commit_count(ref) + def commit_count(ref, options = {}) request = Gitaly::CountCommitsRequest.new( repository: @gitaly_repo, revision: ref ) + request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present? + request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present? + request.path = options[:path] if options[:path].present? GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count end + def last_commit_for_path(revision, path) + request = Gitaly::LastCommitForPathRequest.new( + repository: @gitaly_repo, + revision: GitalyClient.encode(revision), + path: GitalyClient.encode(path.to_s) + ) + + gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit + return unless gitaly_commit + + Gitlab::Git::Commit.new(@repository, gitaly_commit) + end + def between(from, to) request = Gitaly::CommitsBetweenRequest.new( repository: @gitaly_repo, @@ -118,10 +133,53 @@ module Gitlab consume_commits_response(response) end + def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0) + request = Gitaly::CommitsByMessageRequest.new( + repository: @gitaly_repo, + query: query, + revision: revision.to_s.force_encoding(Encoding::ASCII_8BIT), + path: path.to_s.force_encoding(Encoding::ASCII_8BIT), + limit: limit.to_i, + offset: offset.to_i + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request) + consume_commits_response(response) + end + + def languages(ref = nil) + request = Gitaly::CommitLanguagesRequest.new(repository: @gitaly_repo, revision: ref || '') + response = GitalyClient.call(@repository.storage, :commit_service, :commit_languages, request) + + response.languages.map { |l| { value: l.share.round(2), label: l.name, color: l.color, highlight: l.color } } + end + + def raw_blame(revision, path) + request = Gitaly::RawBlameRequest.new( + repository: @gitaly_repo, + revision: revision, + path: path + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request) + response.reduce("") { |memo, msg| memo << msg.data } + end + + def find_commit(revision) + request = Gitaly::FindCommitRequest.new( + repository: @gitaly_repo, + revision: GitalyClient.encode(revision) + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request) + + response.commit + end + private def commit_diff_request_params(commit, options = {}) - parent_id = commit.parents[0]&.id || EMPTY_TREE_ID + parent_id = commit.parent_ids.first || EMPTY_TREE_ID { repository: @gitaly_repo, @@ -134,8 +192,7 @@ module Gitlab def consume_commits_response(response) response.flat_map do |message| message.commits.map do |gitaly_commit| - commit = GitalyClient::Commit.new(@repository, gitaly_commit) - Gitlab::Git::Commit.new(commit) + Gitlab::Git::Commit.new(@repository, gitaly_commit) end end end diff --git a/lib/gitlab/gitaly_client/diff.rb b/lib/gitlab/gitaly_client/diff.rb index d459c9a88fb..54df6304865 100644 --- a/lib/gitlab/gitaly_client/diff.rb +++ b/lib/gitlab/gitaly_client/diff.rb @@ -7,13 +7,13 @@ module Gitlab def initialize(params) params.each do |key, val| - public_send(:"#{key}=", val) + public_send(:"#{key}=", val) # rubocop:disable GitlabSecurity/PublicSend end end def ==(other) FIELDS.all? do |field| - public_send(field) == other.public_send(field) + public_send(field) == other.public_send(field) # rubocop:disable GitlabSecurity/PublicSend end end end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index b0f7548b7dc..919fb68b8c7 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -16,8 +16,7 @@ module Gitlab response.flat_map do |message| message.branches.map do |branch| - gitaly_commit = GitalyClient::Commit.new(@repository, branch.target) - target_commit = Gitlab::Git::Commit.decorate(gitaly_commit) + target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target) Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit) end end @@ -102,8 +101,7 @@ module Gitlab response.flat_map do |message| message.tags.map do |gitaly_tag| if gitaly_tag.target_commit.present? - commit = GitalyClient::Commit.new(@repository, gitaly_tag.target_commit) - gitaly_commit = Gitlab::Git::Commit.new(commit) + gitaly_commit = Gitlab::Git::Commit.decorate(@repository, gitaly_tag.target_commit) end Gitlab::Git::Tag.new( @@ -141,7 +139,7 @@ module Gitlab committer_email: response.commit_committer.email.dup } - Gitlab::Git::Commit.decorate(hash) + Gitlab::Git::Commit.decorate(@repository, hash) end end end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 13e75b256a7..6ad97e62941 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -10,7 +10,7 @@ module Gitlab def exists? request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :exists, request).exists + GitalyClient.call(@storage, :repository_service, :repository_exists, request).exists end def garbage_collect(create_bitmap) @@ -27,6 +27,11 @@ module Gitlab request = Gitaly::RepackIncrementalRequest.new(repository: @gitaly_repo) GitalyClient.call(@storage, :repository_service, :repack_incremental, request) end + + def repository_size + request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo) + GitalyClient.call(@storage, :repository_service, :repository_size, request).size + end end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index a8c0b47e786..266b1a6fece 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -254,7 +254,7 @@ module Gitlab def import_wiki unless project.wiki.repository_exists? wiki = WikiFormatter.new(project) - gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) + gitlab_shell.import_repository(project.repository_storage_path, wiki.disk_path, wiki.import_url) end rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, diff --git a/lib/gitlab/github_import/wiki_formatter.rb b/lib/gitlab/github_import/wiki_formatter.rb index 6c592ff469c..0396122eeb9 100644 --- a/lib/gitlab/github_import/wiki_formatter.rb +++ b/lib/gitlab/github_import/wiki_formatter.rb @@ -7,8 +7,8 @@ module Gitlab @project = project end - def path_with_namespace - "#{project.path_with_namespace}.wiki" + def disk_path + "#{project.disk_path}.wiki" end def import_url diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index 86fb6c51765..f1007daab5d 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -71,7 +71,7 @@ module Gitlab end def config - Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} + Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"} end def gitlab_options diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb index 9e91c135956..eef97f54962 100644 --- a/lib/gitlab/health_checks/fs_shards_check.rb +++ b/lib/gitlab/health_checks/fs_shards_check.rb @@ -10,7 +10,9 @@ module Gitlab def readiness repository_storages.map do |storage_name| begin - if !storage_stat_test(storage_name) + if !storage_circuitbreaker_test(storage_name) + HealthChecks::Result.new(false, 'circuitbreaker tripped', shard: storage_name) + elsif !storage_stat_test(storage_name) HealthChecks::Result.new(false, 'cannot stat storage', shard: storage_name) else with_temp_file(storage_name) do |tmp_file_path| @@ -36,7 +38,8 @@ module Gitlab [ storage_stat_metrics(storage_name), storage_write_metrics(storage_name), - storage_read_metrics(storage_name) + storage_read_metrics(storage_name), + storage_circuitbreaker_metrics(storage_name) ].flatten end end @@ -121,6 +124,12 @@ module Gitlab file_contents == RANDOM_STRING end + def storage_circuitbreaker_test(storage_name) + Gitlab::Git::Storage::CircuitBreaker.new(storage_name).perform { "OK" } + rescue Gitlab::Git::Storage::Inaccessible + nil + end + def storage_stat_metrics(storage_name) operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, shard: storage_name) do with_timing { storage_stat_test(storage_name) } @@ -143,6 +152,14 @@ module Gitlab end end end + + def storage_circuitbreaker_metrics(storage_name) + operation_metrics(:filesystem_circuitbreaker, + :filesystem_circuitbreaker_latency_seconds, + shard: storage_name) do + with_timing { storage_circuitbreaker_test(storage_name) } + end + end end end end diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index cc282d1415b..5d106b5c075 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -16,7 +16,8 @@ module Gitlab 'eo' => 'Esperanto', 'it' => 'Italiano', 'uk' => 'Українська', - 'ja' => '日本語' + 'ja' => '日本語', + 'ko' => '한국어' }.freeze def available_locales diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index c8ad3a7a5e0..c5c05bfe2fb 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -101,6 +101,7 @@ excluded_attributes: merge_requests: - :milestone_id - :ref_fetched + - :merge_jid award_emoji: - :awardable_id statuses: diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index c824d3ea9fc..32ca2809b2f 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -13,7 +13,7 @@ module Gitlab def restore return true unless File.exist?(@path_to_bundle) - gitlab_shell.import_repository(@project.repository_storage_path, @project.path_with_namespace, @path_to_bundle) + gitlab_shell.import_repository(@project.repository_storage_path, @project.disk_path, @path_to_bundle) rescue => e @shared.error(e) false diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb index 62a2553675c..f9ae5079d7c 100644 --- a/lib/gitlab/import_export/uploads_saver.rb +++ b/lib/gitlab/import_export/uploads_saver.rb @@ -24,6 +24,7 @@ module Gitlab end def uploads_path + # TODO: decide what to do with uploads. We will use UUIDs here too? File.join(Rails.root.join('public/uploads'), @project.path_with_namespace) end end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 52276cbcd9a..5404dc11a87 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -8,7 +8,7 @@ module Gitlab ImportSource = Struct.new(:name, :title, :importer) ImportTable = [ - ImportSource.new('github', 'GitHub', Gitlab::GithubImport::Importer), + ImportSource.new('github', 'GitHub', Github::Import), ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer), ImportSource.new('gitlab', 'GitLab.com', Gitlab::GitlabImport::Importer), ImportSource.new('google_code', 'Google Code', Gitlab::GoogleCodeImport::Importer), diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb index b75ae512d92..d9a79f7c291 100644 --- a/lib/gitlab/key_fingerprint.rb +++ b/lib/gitlab/key_fingerprint.rb @@ -1,55 +1,48 @@ module Gitlab class KeyFingerprint - include Gitlab::Popen + attr_reader :key, :ssh_key - attr_accessor :key + # Unqualified MD5 fingerprint for compatibility + delegate :fingerprint, to: :ssh_key, allow_nil: true def initialize(key) @key = key - end - - def fingerprint - cmd_status = 0 - cmd_output = '' - - Tempfile.open('gitlab_key_file') do |file| - file.puts key - file.rewind - - cmd = [] - cmd.push('ssh-keygen') - cmd.push('-E', 'md5') if explicit_fingerprint_algorithm? - cmd.push('-lf', file.path) - - cmd_output, cmd_status = popen(cmd, '/tmp') - end - - return nil unless cmd_status.zero? - # 16 hex bytes separated by ':', optionally starting with "MD5:" - fingerprint_matches = cmd_output.match(/(MD5:)?(?<fingerprint>(\h{2}:){15}\h{2})/) - return nil unless fingerprint_matches - - fingerprint_matches[:fingerprint] + @ssh_key = + begin + Net::SSH::KeyFactory.load_data_public_key(key) + rescue Net::SSH::Exception, NotImplementedError + end end - private - - def explicit_fingerprint_algorithm? - # OpenSSH 6.8 introduces a new default output format for fingerprints. - # Check the version and decide which command to use. - - version_output, version_status = popen(%w(ssh -V)) - return false unless version_status.zero? + def valid? + ssh_key.present? + end - version_matches = version_output.match(/OpenSSH_(?<major>\d+)\.(?<minor>\d+)/) - return false unless version_matches + def type + return unless valid? - version_info = Gitlab::VersionInfo.new(version_matches[:major].to_i, version_matches[:minor].to_i) + parts = ssh_key.ssh_type.split('-') + parts.shift if parts[0] == 'ssh' - required_version_info = Gitlab::VersionInfo.new(6, 8) + parts[0].upcase + end - version_info >= required_version_info + def bits + return unless valid? + + case type + when 'RSA' + ssh_key.n.num_bits + when 'DSS', 'DSA' + ssh_key.p.num_bits + when 'ECDSA' + ssh_key.group.order.num_bits + when 'ED25519' + 256 + else + raise "Unsupported key type: #{type}" + end end end end diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb index 219accfc029..716d20bb91a 100644 --- a/lib/gitlab/metrics/base_sampler.rb +++ b/lib/gitlab/metrics/base_sampler.rb @@ -1,20 +1,7 @@ require 'logger' module Gitlab module Metrics - class BaseSampler - def self.initialize_instance(*args) - raise "#{name} singleton instance already initialized" if @instance - @instance = new(*args) - at_exit(&@instance.method(:stop)) - @instance - end - - def self.instance - @instance - end - - attr_reader :running - + class BaseSampler < Daemon # interval - The sampling interval in seconds. def initialize(interval) interval_half = interval.to_f / 2 @@ -22,44 +9,7 @@ module Gitlab @interval = interval @interval_steps = (-interval_half..interval_half).step(0.1).to_a - @mutex = Mutex.new - end - - def enabled? - true - end - - def start - return unless enabled? - - @mutex.synchronize do - return if running - @running = true - - @thread = Thread.new do - sleep(sleep_interval) - - while running - safe_sample - - sleep(sleep_interval) - end - end - end - end - - def stop - @mutex.synchronize do - return unless running - - @running = false - - if @thread - @thread.wakeup if @thread.alive? - @thread.join - @thread = nil - end - end + super() end def safe_sample @@ -81,7 +31,7 @@ module Gitlab # potentially missing anything that happens in between samples). # 2. Don't sample data at the same interval two times in a row. def sleep_interval - while step = @interval_steps.sample + while (step = @interval_steps.sample) if step != @last_step @last_step = step @@ -89,6 +39,25 @@ module Gitlab end end end + + private + + attr_reader :running + + def start_working + @running = true + sleep(sleep_interval) + + while running + safe_sample + + sleep(sleep_interval) + end + end + + def stop_working + @running = false + end end end end diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb new file mode 100644 index 00000000000..5980a4ded2b --- /dev/null +++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb @@ -0,0 +1,39 @@ +require 'webrick' +require 'prometheus/client/rack/exporter' + +module Gitlab + module Metrics + class SidekiqMetricsExporter < Daemon + def enabled? + Gitlab::Metrics.metrics_folder_present? && settings.enabled + end + + def settings + Settings.monitoring.sidekiq_exporter + end + + private + + attr_reader :server + + def start_working + @server = ::WEBrick::HTTPServer.new(Port: settings.port, BindAddress: settings.address) + server.mount "/", Rack::Handler::WEBrick, rack_app + server.start + end + + def stop_working + server.shutdown + @server = nil + end + + def rack_app + Rack::Builder.app do + use Rack::Deflater + use ::Prometheus::Client::Rack::Exporter + run -> (env) { [404, {}, ['']] } + end + end + end + end +end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 3f2bbd9f6a6..e8330917e91 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -166,12 +166,17 @@ module Gitlab username ||= auth_hash.username email ||= auth_hash.email + valid_username = ::Namespace.clean_path(username) + + uniquify = Uniquify.new + valid_username = uniquify.string(valid_username) { |s| !DynamicPathValidator.valid_user_path?(s) } + name = auth_hash.name - name = ::Namespace.clean_path(username) if name.strip.empty? + name = valid_username if name.strip.empty? { name: name, - username: ::Namespace.clean_path(username), + username: valid_username, email: email, password: auth_hash.password, password_confirmation: auth_hash.password, diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb new file mode 100644 index 00000000000..cf461adf697 --- /dev/null +++ b/lib/gitlab/project_template.rb @@ -0,0 +1,45 @@ +module Gitlab + class ProjectTemplate + attr_reader :title, :name + + def initialize(name, title) + @name, @title = name, title + end + + alias_method :logo, :name + + def file + archive_path.open + end + + def archive_path + Rails.root.join("vendor/project_templates/#{name}.tar.gz") + end + + def clone_url + "https://gitlab.com/gitlab-org/project-templates/#{name}.git" + end + + def ==(other) + name == other.name && title == other.title + end + + TEMPLATES_TABLE = [ + ProjectTemplate.new('rails', 'Ruby on Rails') + ].freeze + + class << self + def all + TEMPLATES_TABLE + end + + def find(name) + all.find { |template| template.name == name.to_s } + end + + def archive_directory + Rails.root.join("vendor_directory/project_templates") + end + end + end +end diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb index 67c69d9ccf3..69d055c901c 100644 --- a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb +++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb @@ -6,14 +6,13 @@ module Gitlab def query(deployment_id) Deployment.find_by(id: deployment_id).try do |deployment| - query_context = { - environment_slug: deployment.environment.slug, - environment_filter: %{container_name!="POD",environment="#{deployment.environment.slug}"}, - timeframe_start: (deployment.created_at - 30.minutes).to_f, - timeframe_end: (deployment.created_at + 30.minutes).to_f - } - - query_metrics(query_context) + query_metrics( + common_query_context( + deployment.environment, + timeframe_start: (deployment.created_at - 30.minutes).to_f, + timeframe_end: (deployment.created_at + 30.minutes).to_f + ) + ) end end end diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb index b5a679ddd79..32fe8201a8d 100644 --- a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb +++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb @@ -5,15 +5,10 @@ module Gitlab include QueryAdditionalMetrics def query(environment_id) - Environment.find_by(id: environment_id).try do |environment| - query_context = { - environment_slug: environment.slug, - environment_filter: %{container_name!="POD",environment="#{environment.slug}"}, - timeframe_start: 8.hours.ago.to_f, - timeframe_end: Time.now.to_f - } - - query_metrics(query_context) + ::Environment.find_by(id: environment_id).try do |environment| + query_metrics( + common_query_context(environment, timeframe_start: 8.hours.ago.to_f, timeframe_end: Time.now.to_f) + ) end end end diff --git a/lib/gitlab/prometheus/queries/environment_query.rb b/lib/gitlab/prometheus/queries/environment_query.rb index 66f29d95177..1d17d3cfd56 100644 --- a/lib/gitlab/prometheus/queries/environment_query.rb +++ b/lib/gitlab/prometheus/queries/environment_query.rb @@ -3,7 +3,7 @@ module Gitlab module Queries class EnvironmentQuery < BaseQuery def query(environment_id) - Environment.find_by(id: environment_id).try do |environment| + ::Environment.find_by(id: environment_id).try do |environment| environment_slug = environment.slug timeframe_start = 8.hours.ago.to_f timeframe_end = Time.now.to_f diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb index e44be770544..7ac6162b54d 100644 --- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb +++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb @@ -42,15 +42,18 @@ module Gitlab end def process_query(context, query) - query_with_result = query.dup + query = query.dup result = if query.key?(:query_range) - client_query_range(query[:query_range] % context, start: context[:timeframe_start], stop: context[:timeframe_end]) + query[:query_range] %= context + client_query_range(query[:query_range], start: context[:timeframe_start], stop: context[:timeframe_end]) else - client_query(query[:query] % context, time: context[:timeframe_end]) + query[:query] %= context + client_query(query[:query], time: context[:timeframe_end]) end - query_with_result[:result] = result&.map(&:deep_symbolize_keys) - query_with_result + + query[:result] = result&.map(&:deep_symbolize_keys) + query end def available_metrics @@ -67,6 +70,16 @@ module Gitlab result.select { |group| group.metrics.any? } end + + def common_query_context(environment, timeframe_start:, timeframe_end:) + { + timeframe_start: timeframe_start, + timeframe_end: timeframe_end, + ci_environment_slug: environment.slug, + kube_namespace: environment.project.kubernetes_service&.actual_namespace || '', + environment_filter: %{container_name!="POD",environment="#{environment.slug}"} + } + end end end end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index f5b757ace77..bc836dcc08d 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -45,7 +45,7 @@ module Gitlab end def all - REFERABLES.each { |referable| send(referable.to_s.pluralize) } + REFERABLES.each { |referable| send(referable.to_s.pluralize) } # rubocop:disable GitlabSecurity/PublicSend @references.values.flatten end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 4366ff336ef..0cb28732402 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -105,12 +105,24 @@ module Gitlab # fetch_remote("gitlab/gitlab-ci", "upstream") # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 - def fetch_remote(storage, name, remote, forced: false, no_tags: false) + def fetch_remote(storage, name, remote, ssh_auth: nil, forced: false, no_tags: false) args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"] args << '--force' if forced args << '--no-tags' if no_tags - gitlab_shell_fast_execute_raise_error(args) + vars = {} + + if ssh_auth&.ssh_import? + if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present? + vars['GITLAB_SHELL_SSH_KEY'] = ssh_auth.ssh_private_key + end + + if ssh_auth.ssh_known_hosts.present? + vars['GITLAB_SHELL_KNOWN_HOSTS'] = ssh_auth.ssh_known_hosts + end + end + + gitlab_shell_fast_execute_raise_error(args, vars) end # Move repository @@ -293,15 +305,15 @@ module Gitlab false end - def gitlab_shell_fast_execute_raise_error(cmd) - output, status = gitlab_shell_fast_execute_helper(cmd) + def gitlab_shell_fast_execute_raise_error(cmd, vars = {}) + output, status = gitlab_shell_fast_execute_helper(cmd, vars) raise Error, output unless status.zero? true end - def gitlab_shell_fast_execute_helper(cmd) - vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS) + def gitlab_shell_fast_execute_helper(cmd, vars = {}) + vars.merge!(ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS)) # Don't pass along the entire parent environment to prevent gitlab-shell # from wasting I/O by searching through GEM_PATH diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index e0ac21305a5..748e0a29184 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -27,8 +27,8 @@ module Gitlab ci_pipeline_schedules: ::Ci::PipelineSchedule.count, deploy_keys: DeployKey.count, deployments: Deployment.count, - environments: Environment.count, - in_review_folder: Environment.in_review_folder.count, + environments: ::Environment.count, + in_review_folder: ::Environment.in_review_folder.count, groups: Group.count, issues: Issue.count, keys: Key.count, diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb new file mode 100644 index 00000000000..05668c69006 --- /dev/null +++ b/lib/haml_lint/inline_javascript.rb @@ -0,0 +1,16 @@ +unless Rails.env.production? + require 'haml_lint/haml_visitor' + require 'haml_lint/linter' + require 'haml_lint/linter_registry' + + module HamlLint + class Linter::InlineJavaScript < Linter + include LinterRegistry + + def visit_filter(node) + return unless node.filter_type == 'javascript' + record_lint(node, 'Inline JavaScript is discouraged (https://docs.gitlab.com/ee/development/gotchas.html#do-not-use-inline-javascript-in-views)') + end + end + end +end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 688a79c0441..ef08bd46e17 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -36,11 +36,12 @@ module Mattermost def with_session with_lease do - raise Mattermost::NoSessionError unless create + create begin yield self - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED => e + Rails.logger.error(e.message + "\n" + e.backtrace.join("\n")) raise Mattermost::NoSessionError ensure destroy @@ -85,10 +86,12 @@ module Mattermost private def create - return unless oauth_uri - return unless token_uri + raise Mattermost::NoSessionError unless oauth_uri + raise Mattermost::NoSessionError unless token_uri @token = request_token + raise Mattermost::NoSessionError unless @token + @headers = { Authorization: "Bearer #{@token}" } @@ -106,11 +109,16 @@ module Mattermost @oauth_uri = nil response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) - return unless 300 <= response.code && response.code < 400 + return unless (300...400) === response.code redirect_uri = response.headers['location'] return unless redirect_uri + oauth_cookie = parse_cookie(response) + @headers = { + Cookie: oauth_cookie.to_cookie_string + } + @oauth_uri = URI.parse(redirect_uri) end @@ -124,7 +132,7 @@ module Mattermost def request_token response = get(token_uri, follow_redirects: false) - if 200 <= response.code && response.code < 400 + if (200...400) === response.code response.headers['token'] end end @@ -156,5 +164,11 @@ module Mattermost rescue Errno::ECONNREFUSED => e raise Mattermost::ConnectionError.new(e.message) end + + def parse_cookie(response) + cookie_hash = CookieHash.new + response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) } + cookie_hash + end end end diff --git a/lib/static_model.rb b/lib/static_model.rb index 185921d8fbe..60e2dd82e4e 100644 --- a/lib/static_model.rb +++ b/lib/static_model.rb @@ -18,7 +18,7 @@ module StaticModel # # Pass it along if we respond to it. def [](key) - send(key) if respond_to?(key) + send(key) if respond_to?(key) # rubocop:disable GitlabSecurity/PublicSend end def to_param diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index c5f93336346..2f2de083dc0 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -291,7 +291,7 @@ start_gitlab() { fi if [ "$gitlab_workhorse_status" = "0" ]; then - echo "The GitLab Workhorse is already running with pid $spid, not restarting" + echo "The GitLab Workhorse is already running with pid $hpid, not restarting" else # No need to remove a socket, gitlab-workhorse does this itself. # Because gitlab-workhorse has multiple executables we need to fix @@ -313,7 +313,7 @@ start_gitlab() { if [ "$gitlab_pages_enabled" = true ]; then if [ "$gitlab_pages_status" = "0" ]; then - echo "The GitLab Pages is already running with pid $spid, not restarting" + echo "The GitLab Pages is already running with pid $gppid, not restarting" else $app_root/bin/daemon_with_pidfile $gitlab_pages_pid_path \ $gitlab_pages_dir/gitlab-pages $gitlab_pages_options \ @@ -421,7 +421,7 @@ print_status() { fi if [ "$gitlab_pages_enabled" = true ]; then if [ "$gitlab_pages_status" = "0" ]; then - echo "The GitLab Pages with pid $mpid is running." + echo "The GitLab Pages with pid $gppid is running." else printf "The GitLab Pages is \033[31mnot running\033[0m.\n" fi diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index d9746c5c1aa..875c8bcbf3c 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -18,8 +18,11 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + proxy_cache off; + # The same address as passed to GitLab Pages: `-listen-proxy` - proxy_pass http://localhost:8090/; + proxy_pass http://localhost:8090/; } # Define custom error pages diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index a1ccf266835..62ed482e2bf 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -67,8 +67,11 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + proxy_cache off; + # The same address as passed to GitLab Pages: `-listen-proxy` - proxy_pass http://localhost:8090/; + proxy_pass http://localhost:8090/; } # Define custom error pages diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 858f1cd7b34..1bd36bbe20a 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -41,8 +41,6 @@ namespace :gitlab do end namespace :gitlab_shell do - include SystemCheck::Helpers - desc "GitLab | Check the configuration of GitLab Shell" task check: :environment do warn_user_is_not_gitlab @@ -249,8 +247,6 @@ namespace :gitlab do end namespace :sidekiq do - include SystemCheck::Helpers - desc "GitLab | Check the configuration of Sidekiq" task check: :environment do warn_user_is_not_gitlab @@ -309,8 +305,6 @@ namespace :gitlab do end namespace :incoming_email do - include SystemCheck::Helpers - desc "GitLab | Check the configuration of Reply by email" task check: :environment do warn_user_is_not_gitlab @@ -444,8 +438,6 @@ namespace :gitlab do end namespace :ldap do - include SystemCheck::Helpers - task :check, [:limit] => :environment do |_, args| # Only show up to 100 results because LDAP directories can be very big. # This setting only affects the `rake gitlab:check` script. @@ -501,8 +493,6 @@ namespace :gitlab do end namespace :repo do - include SystemCheck::Helpers - desc "GitLab | Check the integrity of the repositories managed by GitLab" task check: :environment do Gitlab.config.repositories.storages.each do |name, repository_storage| @@ -517,8 +507,6 @@ namespace :gitlab do end namespace :user do - include SystemCheck::Helpers - desc "GitLab | Check the integrity of a specific user's repositories" task :check_repos, [:username] => :environment do |t, args| username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue)) @@ -527,7 +515,7 @@ namespace :gitlab do repo_dirs = user.authorized_projects.map do |p| File.join( p.repository_storage_path, - "#{p.path_with_namespace}.git" + "#{p.disk_path}.git" ) end diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake index 9df07ea8d83..1f504485e4c 100644 --- a/lib/tasks/gitlab/gitaly.rake +++ b/lib/tasks/gitlab/gitaly.rake @@ -19,7 +19,10 @@ namespace :gitlab do Dir.chdir(args.dir) do create_gitaly_configuration - Bundler.with_original_env { run_command!([command]) } + # In CI we run scripts/gitaly-test-build instead of this command + unless ENV['CI'].present? + Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + [command]) } + end end end @@ -30,7 +33,9 @@ namespace :gitlab do puts "# Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}" puts "# This is in TOML format suitable for use in Gitaly's config.toml file." - puts gitaly_configuration_toml + # Exclude gitaly-ruby configuration because that depends on the gitaly + # installation directory. + puts gitaly_configuration_toml(gitaly_ruby: false) end private @@ -41,7 +46,7 @@ namespace :gitlab do # only generate a configuration for the most common and simplest case: when # we have exactly one Gitaly process and we are sure it is running locally # because it uses a Unix socket. - def gitaly_configuration_toml + def gitaly_configuration_toml(gitaly_ruby: true) storages = [] address = nil @@ -60,6 +65,8 @@ namespace :gitlab do end config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages } config[:auth] = { token: 'secret' } if Rails.env.test? + config[:'gitaly-ruby'] = { dir: File.join(Dir.pwd, 'ruby') } if gitaly_ruby + config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path } TOML.dump(config) end diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake index dd2d5861481..b0a24790c4a 100644 --- a/lib/tasks/gitlab/helpers.rake +++ b/lib/tasks/gitlab/helpers.rake @@ -4,5 +4,5 @@ require 'tasks/gitlab/task_helpers' StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] namespace :gitlab do - include Gitlab::TaskHelpers + extend SystemCheck::Helpers end diff --git a/lib/tasks/gitlab/list_repos.rake b/lib/tasks/gitlab/list_repos.rake index ffcc76e5498..b732db9db6e 100644 --- a/lib/tasks/gitlab/list_repos.rake +++ b/lib/tasks/gitlab/list_repos.rake @@ -9,7 +9,7 @@ namespace :gitlab do scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids) end scope.find_each do |project| - base = File.join(project.repository_storage_path, project.path_with_namespace) + base = File.join(project.repository_storage_path, project.disk_path) puts base + '.git' puts base + '.wiki.git' end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index ee2cdcdea1b..42825f29e32 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -80,7 +80,7 @@ namespace :gitlab do print '-' else if Gitlab::Shell.new.add_repository(project.repository_storage_path, - project.path_with_namespace) + project.disk_path) print '.' else print 'F' diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb index 28b2d86eed2..d85b810ac66 100644 --- a/lib/tasks/gitlab/task_helpers.rb +++ b/lib/tasks/gitlab/task_helpers.rb @@ -5,6 +5,8 @@ module Gitlab TaskAbortedByUserError = Class.new(StandardError) module TaskHelpers + extend self + # Ask if the user wants to continue # # Returns "yes" the user chose to continue diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake index 59c32bbe7a4..a7e30423c7a 100644 --- a/lib/tasks/gitlab/update_templates.rake +++ b/lib/tasks/gitlab/update_templates.rake @@ -4,6 +4,55 @@ namespace :gitlab do TEMPLATE_DATA.each { |template| update(template) } end + desc "GitLab | Update project templates" + task :update_project_templates do + if Rails.env.production? + puts "This rake task is not meant fo production instances".red + exit(1) + end + admin = User.find_by(admin: true) + + unless admin + puts "No admin user could be found".red + exit(1) + end + + Gitlab::ProjectTemplate.all.each do |template| + params = { + import_url: template.clone_url, + namespace_id: admin.namespace.id, + path: template.title, + skip_wiki: true + } + + puts "Creating project for #{template.name}" + project = Projects::CreateService.new(admin, params).execute + + loop do + if project.finished? + puts "Import finished for #{template.name}" + break + end + + if project.failed? + puts "Failed to import from #{project_params[:import_url]}".red + exit(1) + end + + puts "Waiting for the import to finish" + + sleep(5) + project.reload + end + + Projects::ImportExport::ExportService.new(project, admin).execute + FileUtils.cp(project.export_project_path, template.archive_path) + Projects::DestroyService.new(admin, project).execute + puts "Exported #{template.name}".green + end + puts "Done".green + end + def update(template) sub_dir = template.repo_url.match(/([A-Za-z-]+)\.git\z/)[1] dir = File.join(vendor_directory, sub_dir) diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake index 609dfaa48e3..ad2d034b0b4 100644 --- a/lib/tasks/haml-lint.rake +++ b/lib/tasks/haml-lint.rake @@ -1,5 +1,6 @@ unless Rails.env.production? require 'haml_lint/rake_task' + require 'haml_lint/inline_javascript' HamlLint::RakeTask.new end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 50b8e331469..96b8f59242c 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -7,7 +7,7 @@ class GithubImport end def initialize(token, gitlab_username, project_path, extras) - @options = { url: 'https://api.github.com', token: token, verbose: true } + @options = { token: token, verbose: true } @project_path = project_path @current_user = User.find_by_username(gitlab_username) @github_repo = extras.empty? ? nil : extras.first @@ -62,6 +62,7 @@ class GithubImport visibility_level: visibility_level, import_type: 'github', import_source: @repo['full_name'], + import_url: @repo['clone_url'].sub('://', "://#{@options[:token]}@"), skip_wiki: @repo['has_wiki'] ).execute end diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 1774c911d71..85d806e6f20 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-13 08:13-0400\n" +"PO-Revision-Date: 2017-08-03 04:43-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" @@ -1151,6 +1151,9 @@ msgstr "Частен" msgid "VisibilityLevel|Public" msgstr "Публичен" +msgid "VisibilityLevel|Unknown" +msgstr "Неизвестно" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 62dbc2621f4..d688478972d 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-13 08:46-0400\n" +"PO-Revision-Date: 2017-08-03 04:44-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n" "Language: eo\n" @@ -1152,6 +1152,9 @@ msgstr "Privata" msgid "VisibilityLevel|Public" msgstr "Publika" +msgid "VisibilityLevel|Unknown" +msgstr "Nekonata" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" "Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto." diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index 959654c7849..c490933c6d4 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -5,13 +5,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-19 09:45-0400\n" -"Last-Translator: Dremor <egeorget@opmbx.org>\n" "Language-Team: French (https://translate.zanata.org/project/view/GitLab)\n" +"PO-Revision-Date: 2017-08-03 03:35-0400\n" +"Last-Translator: Rémy Coutable <remy@rymai.me>\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" @@ -1161,6 +1161,9 @@ msgstr "Privé" msgid "VisibilityLevel|Public" msgstr "Public" +msgid "VisibilityLevel|Unknown" +msgstr "Inconnu" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" "Vous voulez voir les données ? Merci de contacter un administrateur pour en " diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index d4fac6ab34e..7ba23d84405 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -4,13 +4,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-12 05:45-0400\n" -"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n" "Language-Team: Italian (https://translate.zanata.org/project/view/GitLab)\n" +"PO-Revision-Date: 2017-08-07 10:15-0400\n" +"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n" "Language: it\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -647,6 +647,12 @@ msgstr "Tutto" msgid "PipelineSchedules|Inactive" msgstr "Inattiva" +msgid "PipelineSchedules|Input variable key" +msgstr "Chiave della variabile" + +msgid "PipelineSchedules|Input variable value" +msgstr "Valore della variabile" + msgid "PipelineSchedules|Next Run" msgstr "Prossima esecuzione" @@ -656,12 +662,18 @@ msgstr "Nessuna" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Fornisci una breve descrizione per questa pipeline" +msgid "PipelineSchedules|Remove variable row" +msgstr "Rimuovi riga della variabile" + msgid "PipelineSchedules|Take ownership" msgstr "Prendi possesso" msgid "PipelineSchedules|Target" msgstr "Target" +msgid "PipelineSchedules|Variables" +msgstr "Variabili" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizzato" @@ -1144,6 +1156,9 @@ msgstr "Privato" msgid "VisibilityLevel|Public" msgstr "Pubblico" +msgid "VisibilityLevel|Unknown" +msgstr "Sconosciuto" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" "Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie." @@ -1155,6 +1170,15 @@ msgid "Withdraw Access Request" msgstr "Ritira richiesta d'accesso" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Stai per rimuovere il gruppo %{group_name}.\n" +"I gruppi rimossi NON possono esser ripristinati!\n" +"Sei ASSOLUTAMENTE sicuro?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index 04c61906c73..0b1db651c11 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -8,13 +8,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n" -"PO-Revision-Date: 2017-07-19 09:45-0400\n" -"Last-Translator: YANO Tethurou <tetuyano+zana@gmail.com>\n" +"PO-Revision-Date: 2017-08-06 11:23-0400\n" +"Last-Translator: Taisuke Inoue <taisuke.inoue.jp@gmail.com>\n" +"Language-Team: Japanese "Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n" "Language: ja\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" @@ -29,7 +29,7 @@ msgid_plural "%d commits" msgstr[0] "%d個のコミット" msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link}は%{commit_timeago}前、コミットしました。" +msgstr "%{commit_timeago}に%{commit_author_link}がコミットしました。" msgid "1 pipeline" msgid_plural "%d pipelines" @@ -1107,6 +1107,9 @@ msgstr "プライベート" msgid "VisibilityLevel|Public" msgstr "パブリック" +msgid "VisibilityLevel|Unknown" +msgstr "不明" + msgid "Want to see the data? Please ask an administrator for access." msgstr "このデータを参照したいですか?アクセスするには管理者に問い合わせてください。" diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po new file mode 100644 index 00000000000..0a6fbac0880 --- /dev/null +++ b/locale/ko/gitlab.po @@ -0,0 +1,1210 @@ +# Korean translations for gitlab package. +# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2017. +# Huang Tao <htve@outlook.com>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-08-08 08:32-0400\n" +"Last-Translator: chang-ho,cha <changho.cha@gmail.com>\n" +"Language-Team: Korean (https://translate.zanata.org/project/view/GitLab)\n" +"Language: ko\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Zanata 3.9.6\n" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d 커밋" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s 추가 커밋은 성능 이슈를 방지하기 위해 생략되었습니다." + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_timeago} 에 %{commit_author_link} 님이 커밋하였습니다. " + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 파이프라인" +msgstr[1] "%d 파이프라인" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "지속적인 통합에 관한 그래프 모음" + +msgid "About auto deploy" +msgstr "자동 배포 정보" + +msgid "Active" +msgstr "활성" + +msgid "Activity" +msgstr "활동" + +msgid "Add Changelog" +msgstr "변경 로그 추가" + +msgid "Add Contribution guide" +msgstr "기여 가이드 추가" + +msgid "Add License" +msgstr "라이선스 추가" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "프로필에 SSH 키를 추가하여 SSH를 통해 Pull 하거나 Push합니다." + +msgid "Add new directory" +msgstr "새 디렉토리 추가" + +msgid "Archived project! Repository is read-only" +msgstr "프로젝트가 보관되었습니다! 저장소는 읽기만 가능합니다." + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "이 파이프라인 스케쥴을 삭제 하시겠습니까?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "드래그 & 드롭 또는 %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "브랜치" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"<strong>%{branch_name}</strong> 브랜치가 생성되었습니다. 자동 배포를 설정하려면 GitLab CI Yaml " +"템플릿을 선택하고 변경 사항을 적용하십시오. %{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "브랜치 검색" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "브랜치 변경" + +msgid "Branches" +msgstr "브랜치" + +msgid "Browse Directory" +msgstr "디렉토리 찾아보기" + +msgid "Browse File" +msgstr "파일 찾아보기" + +msgid "Browse Files" +msgstr "파일 찾아보기" + +msgid "Browse files" +msgstr "파일 찾아보기" + +msgid "ByAuthor|by" +msgstr "작성자" + +msgid "CI configuration" +msgstr "CI 설정" + +msgid "Cancel" +msgstr "취소" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "브랜치에서 Pick" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "브랜치에서 Revert" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Cherry-pick" + +msgid "ChangeTypeAction|Revert" +msgstr "Revert" + +msgid "Changelog" +msgstr "변경사항" + +msgid "Charts" +msgstr "차트" + +msgid "Cherry-pick this commit" +msgstr "이 커밋을 Cherry-pick" + +msgid "Cherry-pick this merge request" +msgstr "이 머지 리퀘스트를 Cherry-pick" + +msgid "CiStatusLabel|canceled" +msgstr "취소됨" + +msgid "CiStatusLabel|created" +msgstr "생성되었습니다." + +msgid "CiStatusLabel|failed" +msgstr "실패" + +msgid "CiStatusLabel|manual action" +msgstr "수동 실행" + +msgid "CiStatusLabel|passed" +msgstr "성공" + +msgid "CiStatusLabel|passed with warnings" +msgstr "경고와 함께 성공" + +msgid "CiStatusLabel|pending" +msgstr "대기" + +msgid "CiStatusLabel|skipped" +msgstr "건너 뜀" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "수동 실행 대기 중" + +msgid "CiStatusText|blocked" +msgstr "차단됨" + +msgid "CiStatusText|canceled" +msgstr "취소됨" + +msgid "CiStatusText|created" +msgstr "생성됨" + +msgid "CiStatusText|failed" +msgstr "실패" + +msgid "CiStatusText|manual" +msgstr "매뉴얼" + +msgid "CiStatusText|passed" +msgstr "성공" + +msgid "CiStatusText|pending" +msgstr "보류 중" + +msgid "CiStatusText|skipped" +msgstr "건너 뜀" + +msgid "CiStatus|running" +msgstr "실행 중" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "커밋" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "최근 30 건의 커밋 소요시간 (분)" + +msgid "Commit message" +msgstr "커밋 메시지" + +msgid "CommitBoxTitle|Commit" +msgstr "커밋" + +msgid "CommitMessage|Add %{file_name}" +msgstr "%{file_name} 추가" + +msgid "Commits" +msgstr "커밋" + +msgid "Commits feed" +msgstr "커밋 피드" + +msgid "Commits|History" +msgstr "이력" + +msgid "Committed by" +msgstr "커밋한 사용자" + +msgid "Compare" +msgstr "비교" + +msgid "Contribution guide" +msgstr "기여에 대한 안내" + +msgid "Contributors" +msgstr "기여해 주신 분들" + +msgid "Copy URL to clipboard" +msgstr "URL을 클립보드에 복사" + +msgid "Copy commit SHA to clipboard" +msgstr "커밋의 SHA를 클립보드로 복사합니다" + +msgid "Create New Directory" +msgstr "새 디렉토리 만들기" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "%{protocol}을 (를) 통해 Pull 하거나 Push 할 개인 액세스 토큰을 만드십시오." + +msgid "Create directory" +msgstr "디렉토리 만들기" + +msgid "Create empty bare repository" +msgstr "빈 bare 저장소 만들기" + +msgid "Create merge request" +msgstr "머지 리퀘스트 만들기" + +msgid "Create new..." +msgstr "새로 만들기 ..." + +msgid "CreateNewFork|Fork" +msgstr "포크" + +msgid "CreateTag|Tag" +msgstr "태그" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "개인 액세스 토큰 만들기" + +msgid "Cron Timezone" +msgstr "Cron 시간대" + +msgid "Cron syntax" +msgstr "크론 구문" + +msgid "Custom notification events" +msgstr "사용자 정의 알림 이벤트" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"사용자 정의 알림 수준은 참여 수준과 동일합니다. 맞춤 알림 수준을 사용하면 일부 이벤트에 대한 알림도 받게됩니다. 자세한 내용은 " +"%{notification_link}을 확인하십시오." + +msgid "Cycle Analytics" +msgstr "Cycle Analytics" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "Cycle Analytics는 프로젝트에서 아이디어를 프로덕션으로 옮기는 데 걸리는 시간을 대략적으로 보여줍니다." + +msgid "CycleAnalyticsStage|Code" +msgstr "코드" + +msgid "CycleAnalyticsStage|Issue" +msgstr "이슈" + +msgid "CycleAnalyticsStage|Plan" +msgstr "계획" + +msgid "CycleAnalyticsStage|Production" +msgstr "프로덕션" + +msgid "CycleAnalyticsStage|Review" +msgstr "리뷰" + +msgid "CycleAnalyticsStage|Staging" +msgstr "스테이징" + +msgid "CycleAnalyticsStage|Test" +msgstr "테스트" + +msgid "Define a custom pattern with cron syntax" +msgstr "cron 구문을 사용하여 사용자 정의 패턴 정의" + +msgid "Delete" +msgstr "삭제 " + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "배포" + +msgid "Description" +msgstr "설명" + +msgid "Directory name" +msgstr "디렉토리 이름" + +msgid "Don't show again" +msgstr "다시 표시하지 않음" + +msgid "Download" +msgstr "다운로드" + +msgid "Download tar" +msgstr "tar 다운로드" + +msgid "Download tar.bz2" +msgstr "tar.bz2 다운로드" + +msgid "Download tar.gz" +msgstr "tar.gz 다운로드" + +msgid "Download zip" +msgstr "zip 다운로드" + +msgid "DownloadArtifacts|Download" +msgstr "다운로드" + +msgid "DownloadCommit|Email Patches" +msgstr "이메일 패치" + +msgid "DownloadCommit|Plain Diff" +msgstr "Plain Diff" + +msgid "DownloadSource|Download" +msgstr "다운로드" + +msgid "Edit" +msgstr "편집" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "파이프라인 스케줄 편집 %{id}" + +msgid "Every day (at 4:00am)" +msgstr "매일 (오전 4시에)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "매월 (1일 오전 4시)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "매주 (일요일 오전 4시에)" + +msgid "Failed to change the owner" +msgstr "소유자를 변경하지 못했습니다" + +msgid "Failed to remove the pipeline schedule" +msgstr "파이프라인 스케줄을 제거하지 못했습니다." + +msgid "Files" +msgstr "파일" + +msgid "Filter by commit message" +msgstr "커밋 메시지로 필터" + +msgid "Find by path" +msgstr "경로로 찾기" + +msgid "Find file" +msgstr "파일 찾기" + +msgid "FirstPushedBy|First" +msgstr "처음" + +msgid "FirstPushedBy|pushed by" +msgstr "푸시한 사용자" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "포크" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "포크한 사용자" + +msgid "From issue creation until deploy to production" +msgstr "이슈 생성에서 프로덕션 배포까지" + +msgid "From merge request merge until deploy to production" +msgstr "머지 리퀘스트 머지에서 프로덕션 환경에 배포까지" + +msgid "Go to your fork" +msgstr "당신의 포크로 이동하세요" + +msgid "GoToYourFork|Fork" +msgstr "포크" + +msgid "Home" +msgstr "홈" + +msgid "Housekeeping successfully started" +msgstr "Housekeeping이 성공적으로 시작되었습니다" + +msgid "Import repository" +msgstr "저장소 가져 오기" + +msgid "Interval Pattern" +msgstr "주기 패턴" + +msgid "Introducing Cycle Analytics" +msgstr "Cycle Analytics 소개" + +msgid "Jobs for last month" +msgstr "지난달 Jobs" + +msgid "Jobs for last week" +msgstr "지난주 Jobs" + +msgid "Jobs for last year" +msgstr "지난해 Jobs" + +msgid "LFSStatus|Disabled" +msgstr "Disabled" + +msgid "LFSStatus|Enabled" +msgstr "Enabled" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "최근 %d 일" + +msgid "Last Pipeline" +msgstr "최근 파이프라인" + +msgid "Last Update" +msgstr "최근 업데이트:" + +msgid "Last commit" +msgstr "최근 커밋" + +msgid "Learn more in the" +msgstr "더 자세히 알아보기" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "파이프라인 스케쥴 문서로부터 더 알아보기" + +msgid "Leave group" +msgstr "그룹 떠나기" + +msgid "Leave project" +msgstr "프로젝트에서 나가기" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "최대 %d 이벤트 만 표시하는 것으로 제한됩니다." + +msgid "Median" +msgstr "중앙값" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "SSH 키 추가" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "새 이슈" + +msgid "New Pipeline Schedule" +msgstr "새로운 파이프라인 일정" + +msgid "New branch" +msgstr "새 브랜치" + +msgid "New directory" +msgstr "새 디렉토리" + +msgid "New file" +msgstr "새 파일" + +msgid "New issue" +msgstr "새 이슈" + +msgid "New merge request" +msgstr "새 머지 리퀘스트" + +msgid "New schedule" +msgstr "새 일정" + +msgid "New snippet" +msgstr "새 스니펫" + +msgid "New tag" +msgstr "새 태그 " + +msgid "No repository" +msgstr "저장소 없음" + +msgid "No schedules" +msgstr "일정 없음" + +msgid "Not available" +msgstr "사용할 수 없음" + +msgid "Not enough data" +msgstr "데이터가 충분하지 않습니다." + +msgid "Notification events" +msgstr "알림 이벤트" + +msgid "NotificationEvent|Close issue" +msgstr "이슈 닫기" + +msgid "NotificationEvent|Close merge request" +msgstr "머지 리퀘스트 닫기" + +msgid "NotificationEvent|Failed pipeline" +msgstr "실패한 파이프라인" + +msgid "NotificationEvent|Merge merge request" +msgstr "머지 리퀘스트 머지하기" + +msgid "NotificationEvent|New issue" +msgstr "새 이슈" + +msgid "NotificationEvent|New merge request" +msgstr "새 머지 리퀘스트" + +msgid "NotificationEvent|New note" +msgstr "새 노트" + +msgid "NotificationEvent|Reassign issue" +msgstr "이슈 재지정" + +msgid "NotificationEvent|Reassign merge request" +msgstr "머지 리퀘스트 재 할당" + +msgid "NotificationEvent|Reopen issue" +msgstr "이슈 다시 열기" + +msgid "NotificationEvent|Successful pipeline" +msgstr "성공적인 파이프라인" + +msgid "NotificationLevel|Custom" +msgstr "커스텀" + +msgid "NotificationLevel|Disabled" +msgstr "사용 안 함" + +msgid "NotificationLevel|Global" +msgstr "글로벌" + +msgid "NotificationLevel|On mention" +msgstr "언급" + +msgid "NotificationLevel|Participate" +msgstr "참여" + +msgid "NotificationLevel|Watch" +msgstr "Watch" + +msgid "OfSearchInADropdown|Filter" +msgstr "필터" + +msgid "OpenedNDaysAgo|Opened" +msgstr "열린" + +msgid "Options" +msgstr "옵션 " + +msgid "Owner" +msgstr "소유자" + +msgid "Pipeline" +msgstr "파이프라인" + +msgid "Pipeline Health" +msgstr "파이프라인 상태" + +msgid "Pipeline Schedule" +msgstr "파이프라인 스케쥴" + +msgid "Pipeline Schedules" +msgstr "파이프라인 스케쥴" + +msgid "PipelineCharts|Failed:" +msgstr "실패 :" + +msgid "PipelineCharts|Overall statistics" +msgstr "전체 통계" + +msgid "PipelineCharts|Success ratio:" +msgstr "성공 비율 :" + +msgid "PipelineCharts|Successful:" +msgstr "성공 :" + +msgid "PipelineCharts|Total:" +msgstr "합계 :" + +msgid "PipelineSchedules|Activated" +msgstr "활성화 됨" + +msgid "PipelineSchedules|Active" +msgstr "활성" + +msgid "PipelineSchedules|All" +msgstr "모두" + +msgid "PipelineSchedules|Inactive" +msgstr "비활성" + +msgid "PipelineSchedules|Input variable key" +msgstr "입력 변수 키" + +msgid "PipelineSchedules|Input variable value" +msgstr "입력 변수 값" + +msgid "PipelineSchedules|Next Run" +msgstr "다음 실행" + +msgid "PipelineSchedules|None" +msgstr "없음" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "이 파이프라인에 대한 간단한 설명 제공" + +msgid "PipelineSchedules|Remove variable row" +msgstr "변수 행 제거" + +msgid "PipelineSchedules|Take ownership" +msgstr "소유권 가져 오기" + +msgid "PipelineSchedules|Target" +msgstr "대상" + +msgid "PipelineSchedules|Variables" +msgstr "변수" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "사용자 정의" + +msgid "Pipelines" +msgstr "파이프라인" + +msgid "Pipelines charts" +msgstr "파이프라인 차트" + +msgid "Pipeline|all" +msgstr "모두" + +msgid "Pipeline|success" +msgstr "성공" + +msgid "Pipeline|with stage" +msgstr "스테이징" + +msgid "Pipeline|with stages" +msgstr "스테이징" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "'%{project_name}'프로젝트가 삭제 처리 중입니다." + +msgid "Project '%{project_name}' was successfully created." +msgstr "'%{project_name}'프로젝트가 성공적으로 생성되었습니다." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "'%{project_name}'프로젝트가 성공적으로 업데이트되었습니다." + +msgid "Project '%{project_name}' will be deleted." +msgstr "'%{project_name}'프로젝트가 삭제됩니다." + +msgid "Project access must be granted explicitly to each user." +msgstr "프로젝트 액세스는 각 사용자에게 명시적으로 부여되어야합니다." + +msgid "Project export could not be deleted." +msgstr "프로젝트 내보내기를 삭제할 수 없습니다." + +msgid "Project export has been deleted." +msgstr "프로젝트 내보내기가 삭제되었습니다." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "프로젝트 내보내기 링크가 만료되었습니다. 프로젝트 설정에서 새 내보내기를 생성하십시오." + +msgid "Project export started. A download link will be sent by email." +msgstr "프로젝트 내보내기가 시작되었습니다. 다운로드 링크는 이메일로 전송됩니다." + +msgid "Project home" +msgstr "프로젝트 홈" + +msgid "ProjectFeature|Disabled" +msgstr "사용 안 함" + +msgid "ProjectFeature|Everyone with access" +msgstr "접근 권한을 가진 모든 이들" + +msgid "ProjectFeature|Only team members" +msgstr "팀원 만" + +msgid "ProjectFileTree|Name" +msgstr "이름" + +msgid "ProjectLastActivity|Never" +msgstr "Never" + +msgid "ProjectLifecycle|Stage" +msgstr "스테이징" + +msgid "ProjectNetworkGraph|Graph" +msgstr "그래프" + +msgid "Read more" +msgstr "더 읽기" + +msgid "Readme" +msgstr "Readme" + +msgid "RefSwitcher|Branches" +msgstr "브랜치" + +msgid "RefSwitcher|Tags" +msgstr "태그" + +msgid "Related Commits" +msgstr "관련 커밋" + +msgid "Related Deployed Jobs" +msgstr "관련 배포 된 작업" + +msgid "Related Issues" +msgstr "관련 이슈" + +msgid "Related Jobs" +msgstr "관련 Jobs" + +msgid "Related Merge Requests" +msgstr "관련 머지 리퀘스트" + +msgid "Related Merged Requests" +msgstr "관련 머지 리퀘스트" + +msgid "Remind later" +msgstr "나중에 다시 알림" + +msgid "Remove project" +msgstr "프로젝트 삭제" + +msgid "Request Access" +msgstr "액세스 요청" + +msgid "Revert this commit" +msgstr "이 커밋 되돌리기" + +msgid "Revert this merge request" +msgstr "이 머지 리퀘스트 되돌리기" + +msgid "Save pipeline schedule" +msgstr "파이프라인 스케줄 저장" + +msgid "Schedule a new pipeline" +msgstr "새로운 파이프라인 스케줄 잡기" + +msgid "Scheduling Pipelines" +msgstr "파이프라인 스케줄링" + +msgid "Search branches and tags" +msgstr "브랜치 및 태그 검색" + +msgid "Select Archive Format" +msgstr "아카이브 포맷 선택" + +msgid "Select a timezone" +msgstr "시간대 선택" + +msgid "Select target branch" +msgstr "대상 브랜치 선택" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "%{protocol} 프로토콜을 통해 Pull 하거나 Push하려면 계정에 패스워드를 설정하십시오." + +msgid "Set up CI" +msgstr "CI 설정" + +msgid "Set up Koding" +msgstr "Koding 설정" + +msgid "Set up auto deploy" +msgstr "자동 배포 설정" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "패스워드 설정" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "%d 개의 이벤트 표시 중" + +msgid "Source code" +msgstr "소스 코드" + +msgid "StarProject|Star" +msgstr "별표" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "이 변경 사항으로 %{new_merge_request} 을 시작하십시오." + +msgid "Switch branch/tag" +msgstr "스위치 브랜치/태그" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "태그" + +msgid "Tags" +msgstr "태그 " + +msgid "Target Branch" +msgstr "대상 브랜치" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" +"Coding Stage는 첫 번째 커밋에서부터 머지 리퀘스트 생성까지의 시간을 보여줍니다. 첫 번째 머지 리퀘스트을 생성하면 데이터가 " +"자동으로 여기에 추가됩니다." + +msgid "The collection of events added to the data gathered for that stage." +msgstr "해당 단계에서 수집 된 데이터가 이벤트 모음에 추가되었습니다." + +msgid "The fork relationship has been removed." +msgstr "포크 관계가 제거되었습니다." + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"이슈 단계에는 이슈를 작성하여 마일스톤으로 지정하는 데 걸리는 시간 또는 이슈 보드의 목록에 이슈를 추가하는 시간이 표시됩니다. 이 " +"단계의 데이터를 보기 위해서는 이슈를 먼저 작성해야 합니다." + +msgid "The phase of the development lifecycle." +msgstr "개발 수명주기의 단계." + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"파이프라인 일정은 미래에 특정 브랜치 또는 태그에 대해 반복적으로 파이프라인을 실행합니다. 예정된 파이프라인은 관련 사용자를 기반으로 " +"제한된 프로젝트 액세스 권한을 상속받습니다." + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "계획 단계에서는 이전 단계에서 첫 번째 커밋 시간이 표시됩니다. 이 시간은 첫 번째 커밋을 누르면 자동으로 추가됩니다." + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" +"프로덕션 단계에서는 문제를 만들고 코드를 프로덕션 환경으로 배포하는 데 걸리는 총 시간을 보여줍니다. 생산주기에 대한 완전한 아이디어를 " +"얻은 후에는 데이터가 자동으로 추가됩니다." + +msgid "The project can be accessed by any logged in user." +msgstr "이 프로젝트는 로그인 한 사용자가만 액세스 할 수 있습니다." + +msgid "The project can be accessed without any authentication." +msgstr "이 프로젝트는 인증없이 액세스 할 수 있습니다." + +msgid "The repository for this project does not exist." +msgstr "이 프로젝트의 저장소가 존재하지 않습니다." + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" +"Review 단계에서는 머지 리퀘스트를 작성한 후 머지하기까지의 시간을 보여줍니다. 데이터는 첫 번째 머지 리퀘스트을 머지 한 후에 " +"자동으로 추가됩니다." + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" +"Staging 단계에서는 MR 머지과 프로덕션 환경에 코드 배포 사이의 시간을 보여줍니다. 데이터를 Production 환경에 처음 " +"배포하면 데이터가 자동으로 추가됩니다." + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" +"테스트 단계에서는 GitLab CI가 관련 머지 리퀘스트을 위해 모든 파이프라인을 실행하는 데 걸리는 시간을 보여줍니다. 첫 번째 " +"파이프라인 실행이 완료되면 데이터가 자동으로 추가됩니다." + +msgid "The time taken by each data entry gathered by that stage." +msgstr "해당 단계에서 수집 한 각 데이터 입력에 소요 된 시간" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" +"값은 일련의 관측 값 중점에 있습니다. 예를 들어, 3, 5, 9 사이의 중간 값은 5입니다. 3, 5, 7, 8 사이의 중간 값은 (5 " +"+ 7) / 2 = 6입니다." + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "즉, 빈 저장소를 만들거나 기존 저장소를 가져올 때까지 코드를 Push 할 수 없습니다." + +msgid "Time before an issue gets scheduled" +msgstr "이슈가 스케줄되기 전의 시간" + +msgid "Time before an issue starts implementation" +msgstr "이슈가 구현되기 전의 시간" + +msgid "Time between merge request creation and merge/close" +msgstr "머지 리퀘스트 생성과 머지 / 닫기 사이의 시간" + +msgid "Time until first merge request" +msgstr "첫 번째 머지 리퀘스트까지의 시간" + +msgid "Timeago|%s days ago" +msgstr "%s 일 전" + +msgid "Timeago|%s days remaining" +msgstr "%s 일 남음" + +msgid "Timeago|%s hours remaining" +msgstr "%s 시간 남음" + +msgid "Timeago|%s minutes ago" +msgstr "%s 분 전" + +msgid "Timeago|%s minutes remaining" +msgstr "%s 분 남음" + +msgid "Timeago|%s months ago" +msgstr "%s 개월 전" + +msgid "Timeago|%s months remaining" +msgstr "%s 개월 남음" + +msgid "Timeago|%s seconds remaining" +msgstr "%s 초 남음" + +msgid "Timeago|%s weeks ago" +msgstr "%s 주 전" + +msgid "Timeago|%s weeks remaining" +msgstr "%s 주 남음" + +msgid "Timeago|%s years ago" +msgstr "%s 년 전" + +msgid "Timeago|%s years remaining" +msgstr "%s 년 남음" + +msgid "Timeago|1 day remaining" +msgstr "1 일 남음" + +msgid "Timeago|1 hour remaining" +msgstr "1 시간 남음" + +msgid "Timeago|1 minute remaining" +msgstr "1 분 남음" + +msgid "Timeago|1 month remaining" +msgstr "1 개월 남음" + +msgid "Timeago|1 week remaining" +msgstr "1 주 남음" + +msgid "Timeago|1 year remaining" +msgstr "1 년 남음" + +msgid "Timeago|Past due" +msgstr "기한 초과" + +msgid "Timeago|a day ago" +msgstr "1 일 전" + +msgid "Timeago|a month ago" +msgstr "1 달 전" + +msgid "Timeago|a week ago" +msgstr "1 주일 전" + +msgid "Timeago|a while" +msgstr "잠시 전" + +msgid "Timeago|a year ago" +msgstr "1 년 전" + +msgid "Timeago|about %s hours ago" +msgstr "약 %s 시간 전" + +msgid "Timeago|about a minute ago" +msgstr "약 1 분 전" + +msgid "Timeago|about an hour ago" +msgstr "약 1 시간 전" + +msgid "Timeago|in %s days" +msgstr "%s 일 이내" + +msgid "Timeago|in %s hours" +msgstr "%s 시간 이내" + +msgid "Timeago|in %s minutes" +msgstr "%s 분 이내" + +msgid "Timeago|in %s months" +msgstr "%s 개월 이내" + +msgid "Timeago|in %s seconds" +msgstr "%s 초 이내" + +msgid "Timeago|in %s weeks" +msgstr "%s 주 이내" + +msgid "Timeago|in %s years" +msgstr "%s 년 이내" + +msgid "Timeago|in 1 day" +msgstr "1 일 이내" + +msgid "Timeago|in 1 hour" +msgstr "1 시간 이내" + +msgid "Timeago|in 1 minute" +msgstr "1 분 이내" + +msgid "Timeago|in 1 month" +msgstr "1 개월 이내" + +msgid "Timeago|in 1 week" +msgstr "1 주일 이내" + +msgid "Timeago|in 1 year" +msgstr "1 년 이내" + +msgid "Timeago|less than a minute ago" +msgstr "1 분미만" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "시간" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "분" + +msgid "Time|s" +msgstr "초" + +msgid "Total Time" +msgstr "시간 합계:" + +msgid "Total test time for all commits/merges" +msgstr "모든 커밋 / 머지의 총 테스트 시간" + +msgid "Unstar" +msgstr "별표 제거" + +msgid "Upload New File" +msgstr "새 파일 업로드" + +msgid "Upload file" +msgstr "파일 업로드" + +msgid "UploadLink|click to upload" +msgstr "업로드하려면 클릭하십시오." + +msgid "Use your global notification setting" +msgstr "전체 알림 설정 사용" + +msgid "View open merge request" +msgstr "열린 머지 리퀘스트보기" + +msgid "VisibilityLevel|Internal" +msgstr "내부" + +msgid "VisibilityLevel|Private" +msgstr "Private" + +msgid "VisibilityLevel|Public" +msgstr "Public" + +msgid "VisibilityLevel|Unknown" +msgstr "알 수 없음" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "이 데이터를 보고 싶은가요? 관리자에게 액세스 권한을 요청하세요." + +msgid "We don't have enough data to show this stage." +msgstr "이 단계를 보여주기에 충분한 데이터가 없습니다." + +msgid "Withdraw Access Request" +msgstr "액세스 요청 철회" + +msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "%{group_name} 그룹을 제거하려고합니다.\n" +"\"정말로\" 확실합니까?" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"%{project_name_with_namespace} 프로젝트를 삭제하려고합니다.\n" +"삭제된 프로젝트를 복원 할 수 없습니다!\n" +"\"정말로\" 확실합니까?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "포크 관계를 소스 프로젝트 %{forked_from_project}에 대해 제거하려고합니다. \"정말로\" 확실합니까?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "%{project_name_with_namespace}을 다른 소유자에게 이전하려고합니다. \"정말로\" 확실합니까?" + +msgid "You can only add files when you are on a branch" +msgstr "브랜치에 있을 때에만 파일을 추가 할 수 있습니다." + +msgid "You have reached your project limit" +msgstr "프로젝트 숫자 한도에 도달했습니다." + +msgid "You must sign in to star a project" +msgstr "프로젝트에 별표를 표시하려면 로그인 해야 합니다." + +msgid "You need permission." +msgstr "권한이 필요합니다." + +msgid "You will not get any notifications via email" +msgstr "이메일로 알림을 받지 않습니다." + +msgid "You will only receive notifications for the events you choose" +msgstr "선택한 이벤트에 대한 알림만 받습니다." + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "참여한 스레드에 대한 알림만 받습니다." + +msgid "You will receive notifications for any activity" +msgstr "모든 활동에 대한 알림을 받게됩니다." + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "당신은 당신이 @mentioned 한 코멘트에 대해서만 통지를 받게됩니다." + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"당신의 계정에 %{set_password_link} 을 하기 전에는 %{protocol} 프로토콜을 통해 프로젝트 코드를 Pull 하거나 " +"Push 할 수 없습니다" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"당신의 프로필에 %{add_ssh_key_link} 를 하기 전에는 SSH를 통해 프로젝트 코드를 Pull 하거나 Push 할 수 " +"없습니다" + +msgid "Your name" +msgstr "귀하의 이름" + +msgid "day" +msgid_plural "days" +msgstr[0] "일" + +msgid "new merge request" +msgstr "새 머지 리퀘스트" + +msgid "notification emails" +msgstr "알림 이메일" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "부모" + diff --git a/locale/ko/gitlab.po.time_stamp b/locale/ko/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/ko/gitlab.po.time_stamp diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index ee00b816b84..2eaadb64124 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -6,13 +6,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n" -"PO-Revision-Date: 2017-07-14 01:17-0400\n" -"Last-Translator: Huang Tao <htve@outlook.com>\n" +"PO-Revision-Date: 2017-08-03 11:29-0400\n" +"Last-Translator: Alexandre Alencar <alexandre.alencar@gmail.com>\n" "Language: pt-BR\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -42,7 +42,7 @@ msgid "A collection of graphs regarding Continuous Integration" msgstr "Uma coleção de gráficos sobre Integração Contínua" msgid "About auto deploy" -msgstr "Sobre a implantação automática" +msgstr "Sobre o deploy automático" msgid "Active" msgstr "Ativo" @@ -84,15 +84,15 @@ msgid "" "choose a GitLab CI Yaml template and commit your changes. " "%{link_to_autodeploy_doc}" msgstr "" -"O branch <strong>%{branch_name}</strong> foi criado. Para configurar a " -"implantação automática, selecione um modelo de Yaml do GitLab CI e registre " -"suas mudanças. %{link_to_autodeploy_doc}" +"O branch <strong>%{branch_name}</strong> foi criado. Para configurar o " +"deploy automático, selecione um modelo de Yaml do GitLab CI e commit suas " +"mudanças. %{link_to_autodeploy_doc}" msgid "BranchSwitcherPlaceholder|Search branches" -msgstr "BranchSwitcherPlaceholder|Procurar por branches" +msgstr "Procurar por branches" msgid "BranchSwitcherTitle|Switch branch" -msgstr "BranchSwitcherTitle|Mudar de branch" +msgstr "Mudar de branch" msgid "Branches" msgstr "Branches" @@ -113,7 +113,7 @@ msgid "ByAuthor|by" msgstr "por" msgid "CI configuration" -msgstr "Configuração da Integração Contínua" +msgstr "Configuração da IC" msgid "Cancel" msgstr "Cancelar" @@ -269,7 +269,7 @@ msgid "CreateTag|Tag" msgstr "Tag" msgid "CreateTokenToCloneLink|create a personal access token" -msgstr "CreateTokenToCloneLink|criar um token de acesso pessoal" +msgstr "criar um token de acesso pessoal" msgid "Cron Timezone" msgstr "Fuso horário do cron" @@ -419,8 +419,7 @@ msgid "From issue creation until deploy to production" msgstr "Da abertura de tarefas até a implantação para a produção" msgid "From merge request merge until deploy to production" -msgstr "" -"Do merge request até a implantação em produção" +msgstr "Do merge request até a implantação em produção" msgid "Go to your fork" msgstr "Ir para seu fork" @@ -618,19 +617,19 @@ msgid "Pipeline Schedules" msgstr "Agendamentos da Pipeline" msgid "PipelineCharts|Failed:" -msgstr "PipelineCharts|Falhou:" +msgstr "Falhou:" msgid "PipelineCharts|Overall statistics" -msgstr "PipelineCharts|Estatísticas gerais" +msgstr "Estatísticas gerais" msgid "PipelineCharts|Success ratio:" -msgstr "PipelineCharts|Taxa de sucesso:" +msgstr "Taxa de sucesso:" msgid "PipelineCharts|Successful:" -msgstr "PipelineCharts|Sucesso:" +msgstr "Sucesso:" msgid "PipelineCharts|Total:" -msgstr "PipelineCharts|Total:" +msgstr "Total:" msgid "PipelineSchedules|Activated" msgstr "Ativado" @@ -645,10 +644,10 @@ msgid "PipelineSchedules|Inactive" msgstr "Inativo" msgid "PipelineSchedules|Input variable key" -msgstr "PipelineSchedules|Chave da variável de entrada" +msgstr "Chave da variável de entrada" msgid "PipelineSchedules|Input variable value" -msgstr "PipelineSchedules|Valor da variável de entrada" +msgstr "Valor da variável de entrada" msgid "PipelineSchedules|Next Run" msgstr "Próxima Execução" @@ -660,7 +659,7 @@ msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Digite uma descrição curta para esta pipeline" msgid "PipelineSchedules|Remove variable row" -msgstr "PipelineSchedules|Remova a linha da variável" +msgstr "Remova a linha da variável" msgid "PipelineSchedules|Take ownership" msgstr "Tornar-se proprietário" @@ -669,7 +668,7 @@ msgid "PipelineSchedules|Target" msgstr "Destino" msgid "PipelineSchedules|Variables" -msgstr "PipelineSchedules|Variáveis" +msgstr "Variáveis" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizado" @@ -681,10 +680,10 @@ msgid "Pipelines charts" msgstr "Gráficos de pipelines" msgid "Pipeline|all" -msgstr "Pipeline|todos" +msgstr "todos" msgid "Pipeline|success" -msgstr "Pipeline|sucesso" +msgstr "sucesso" msgid "Pipeline|with stage" msgstr "com etapa" @@ -974,7 +973,7 @@ msgid "Time before an issue gets scheduled" msgstr "Tempo até que uma issue seja agendada" msgid "Time before an issue starts implementation" -msgstr "Tempo até que uma issue comece a ser implementada" +msgstr "Tempo até que uma issue comece a ser implementado" msgid "Time between merge request creation and merge/close" msgstr "" @@ -1136,7 +1135,7 @@ msgid "Upload file" msgstr "Enviar arquivo" msgid "UploadLink|click to upload" -msgstr "UploadLink|clique para fazer upload" +msgstr "clique para fazer upload" msgid "Use your global notification setting" msgstr "Utilizar configuração de notificação global" @@ -1153,6 +1152,9 @@ msgstr "Privado" msgid "VisibilityLevel|Public" msgstr "Público" +msgid "VisibilityLevel|Unknown" +msgstr "Desconhecido" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Precisa visualizar os dados? Solicite acesso ao administrador." diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 4643bed98e2..78f7b059077 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -4,13 +4,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-11 05:13-0400\n" -"Last-Translator: SAS <Stepanov.sa@bashkortostan.ru>\n" "Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n" +"PO-Revision-Date: 2017-08-06 11:23-0400\n" +"Last-Translator: Андрей П. <fenixnow33@gmail.com>\n" "Language: ru\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " @@ -32,20 +32,20 @@ msgstr[2] "" msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d коммит" -msgstr[1] "%d коммит(а|ов)" -msgstr[2] "%d коммит(а|ов)" +msgstr[1] "%d коммитов" +msgstr[2] "%d коммитов" msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} закоммичено %{commit_timeago}" +msgstr "%{commit_author_link} коммичено %{commit_timeago}" msgid "1 pipeline" msgid_plural "%d pipelines" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "1 конвейер" +msgstr[1] "%d конвейеры" +msgstr[2] "%d конвейеры" msgid "A collection of graphs regarding Continuous Integration" -msgstr "" +msgstr "Графики относительно непрерывной интеграции" msgid "About auto deploy" msgstr "Автоматическое развертывание" @@ -57,10 +57,10 @@ msgid "Activity" msgstr "Активность" msgid "Add Changelog" -msgstr "Добавить в журнал изменений" +msgstr "Добавить журнал изменений" msgid "Add Contribution guide" -msgstr "Добавить руководство для контрибьютеров" +msgstr "Добавить руководство" msgid "Add License" msgstr "Добавить лицензию" @@ -71,7 +71,7 @@ msgstr "" "SSH." msgid "Add new directory" -msgstr "Добавить новую директорию" +msgstr "Добавить каталог" msgid "Archived project! Repository is read-only" msgstr "Архивный проект! Репозиторий доступен только для чтения" @@ -98,16 +98,16 @@ msgstr "" "%{link_to_autodeploy_doc}" msgid "BranchSwitcherPlaceholder|Search branches" -msgstr "BranchSwitcherPlaceholder|Поиск веток" +msgstr "Поиск веток" msgid "BranchSwitcherTitle|Switch branch" -msgstr "BranchSwitcherTitle|Переключить ветку" +msgstr "Переключить ветку" msgid "Branches" msgstr "Ветки" msgid "Browse Directory" -msgstr "Просмотр директории" +msgstr "Обзор" msgid "Browse File" msgstr "Просмотр файла" @@ -119,7 +119,7 @@ msgid "Browse files" msgstr "Просмотр файлов" msgid "ByAuthor|by" -msgstr "ByAuthor|по автору" +msgstr "по автору" msgid "CI configuration" msgstr "Настройка CI" @@ -128,22 +128,22 @@ msgid "Cancel" msgstr "Отмена" msgid "ChangeTypeActionLabel|Pick into branch" -msgstr "ChangeTypeActionLabel|Выбрать в ветке" +msgstr "Выбрать в ветке" msgid "ChangeTypeActionLabel|Revert in branch" -msgstr "ChangeTypeActionLabel|Отменить в ветке" +msgstr "Отменить в ветке" msgid "ChangeTypeAction|Cherry-pick" -msgstr "ChangeTypeAction|Подобрать" +msgstr "Подобрать" msgid "ChangeTypeAction|Revert" -msgstr "ChangeTypeAction|Отменить" +msgstr "Отменить" msgid "Changelog" msgstr "Журнал изменений" msgid "Charts" -msgstr "Графики" +msgstr "Диаграммы" msgid "Cherry-pick this commit" msgstr "Подобрать в этом коммите" @@ -152,58 +152,58 @@ msgid "Cherry-pick this merge request" msgstr "Побрать в этом запросе на слияние" msgid "CiStatusLabel|canceled" -msgstr "CiStatusLabel|отменено" +msgstr "отменено" msgid "CiStatusLabel|created" -msgstr "CiStatusLabel|создано" +msgstr "создано" msgid "CiStatusLabel|failed" -msgstr "CiStatusLabel|неудачно" +msgstr "неудачно" msgid "CiStatusLabel|manual action" -msgstr "CiStatusLabel|ручное действие" +msgstr "ручное действие" msgid "CiStatusLabel|passed" -msgstr "CiStatusLabel|пройдено" +msgstr "пройдено" msgid "CiStatusLabel|passed with warnings" -msgstr "CiStatusLabel|пройдено с предупреждениями" +msgstr "пройдено с предупреждениями" msgid "CiStatusLabel|pending" -msgstr "CiStatusLabel|в ожидании" +msgstr "в ожидании" msgid "CiStatusLabel|skipped" -msgstr "CiStatusLabel|пропущено" +msgstr "пропущено" msgid "CiStatusLabel|waiting for manual action" -msgstr "CiStatusLabel|ожидание ручных действий" +msgstr "ожидание ручных действий" msgid "CiStatusText|blocked" -msgstr "CiStatusText|блокировано" +msgstr "блокировано" msgid "CiStatusText|canceled" -msgstr "CiStatusText|отменено" +msgstr "отменено" msgid "CiStatusText|created" -msgstr "CiStatusText|создано" +msgstr "создано" msgid "CiStatusText|failed" -msgstr "CiStatusText|неудачно" +msgstr "неудачно" msgid "CiStatusText|manual" -msgstr "CiStatusText|ручное" +msgstr "ручное" msgid "CiStatusText|passed" -msgstr "CiStatusText|пройдено" +msgstr "пройдено" msgid "CiStatusText|pending" -msgstr "CiStatusText|в ожидании" +msgstr "в ожидании" msgid "CiStatusText|skipped" -msgstr "CiStatusText|пропущено" +msgstr "пропущено" msgid "CiStatus|running" -msgstr "CiStatus|выполняется" +msgstr "выполняется" msgid "Commit" msgid_plural "Commits" @@ -212,37 +212,37 @@ msgstr[1] "Коммиты" msgstr[2] "Коммиты" msgid "Commit duration in minutes for last 30 commits" -msgstr "" +msgstr "Продолжительность последних 30 фиксаций(коммитов) в минутах" msgid "Commit message" msgstr "Описание коммита" msgid "CommitBoxTitle|Commit" -msgstr "CommitBoxTitle|Коммит" +msgstr "Коммит" msgid "CommitMessage|Add %{file_name}" -msgstr "CommitMessage|Добавить %{file_name}" +msgstr "Добавлен %{file_name}" msgid "Commits" msgstr "Коммиты" msgid "Commits feed" -msgstr "" +msgstr "Фиксировать подачу" msgid "Commits|History" -msgstr "Commits|История" +msgstr "История" msgid "Committed by" -msgstr "Коммит" +msgstr "Фиксировано" msgid "Compare" -msgstr "Сравнение" +msgstr "Сравнить" msgid "Contribution guide" -msgstr "Руководство контрибьютора" +msgstr "Руководство участника" msgid "Contributors" -msgstr "Контрибьюторы" +msgstr "Участники" msgid "Copy URL to clipboard" msgstr "Копировать URL в буфер обмена" @@ -251,18 +251,20 @@ msgid "Copy commit SHA to clipboard" msgstr "Копировать SHA коммита в буфер обмена" msgid "Create New Directory" -msgstr "Создать новую директорию" +msgstr "Создать директорию" msgid "" "Create a personal access token on your account to pull or push via " "%{protocol}." msgstr "" +"Создать личный токен на аккаунте для получения или отправки через " +"%{protocol}." msgid "Create directory" msgstr "Создать директорию" msgid "Create empty bare repository" -msgstr "Создать пустой пустой репозиторий" +msgstr "Создать пустой репозиторий" msgid "Create merge request" msgstr "Создать запрос на объединение" @@ -271,13 +273,13 @@ msgid "Create new..." msgstr "Новый" msgid "CreateNewFork|Fork" -msgstr "CreateNewFork|Форк" +msgstr "Форк" msgid "CreateTag|Tag" -msgstr "CreateTag|Тэг" +msgstr "Тег" msgid "CreateTokenToCloneLink|create a personal access token" -msgstr "" +msgstr "создать персональный токен доступа" msgid "Cron Timezone" msgstr "Временная зона Cron" @@ -299,33 +301,35 @@ msgstr "" "посмотрите %{notification_link}." msgid "Cycle Analytics" -msgstr "Аналитика цикла разработки" +msgstr "Цикл Аналитик" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." msgstr "" +"Цикл Аналитик дает представление о том, сколько времени требуется, чтобы " +"перейти от идеи к производству в проекте." msgid "CycleAnalyticsStage|Code" -msgstr "CycleAnalyticsStage|Код" +msgstr "Написание кода" msgid "CycleAnalyticsStage|Issue" -msgstr "CycleAnalyticsStage|Обращение" +msgstr "Обращение" msgid "CycleAnalyticsStage|Plan" -msgstr "" +msgstr "Планирование" msgid "CycleAnalyticsStage|Production" -msgstr "" +msgstr "Производство" msgid "CycleAnalyticsStage|Review" -msgstr "CycleAnalyticsStage|Ревьюв" +msgstr "Контроль" msgid "CycleAnalyticsStage|Staging" -msgstr "" +msgstr "Постановка" msgid "CycleAnalyticsStage|Test" -msgstr "" +msgstr "Тестирование" msgid "Define a custom pattern with cron syntax" msgstr "Определить настраиваемый шаблон с синтаксисом cron" @@ -335,45 +339,45 @@ msgstr "Удалить" msgid "Deploy" msgid_plural "Deploys" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Разместить" +msgstr[1] "Размещение" +msgstr[2] "Размещение" msgid "Description" msgstr "Описание" msgid "Directory name" -msgstr "Наименование директории" +msgstr "Каталог" msgid "Don't show again" msgstr "Не показывать снова" msgid "Download" -msgstr "Загрузить" +msgstr "Скачать" msgid "Download tar" -msgstr "Загрузить tar" +msgstr "Скачать tar" msgid "Download tar.bz2" -msgstr "Загрузить tar.bz2" +msgstr "Скачать tar.bz2" msgid "Download tar.gz" -msgstr "Загрузить tar.gz" +msgstr "Скачать tar.gz" msgid "Download zip" -msgstr "Загрузить zip" +msgstr "Скачать zip" msgid "DownloadArtifacts|Download" -msgstr "DownloadArtifacts|Загрузка" +msgstr "Скачать" msgid "DownloadCommit|Email Patches" -msgstr "DownloadCommit|Email-патчи" +msgstr "Email-патчи" msgid "DownloadCommit|Plain Diff" -msgstr "DownloadCommit|Plain Diff" +msgstr "Простой Diff" msgid "DownloadSource|Download" -msgstr "DownloadSource|Загрузка" +msgstr "Скачать" msgid "Edit" msgstr "Редактировать" @@ -409,10 +413,10 @@ msgid "Find file" msgstr "Найти файл" msgid "FirstPushedBy|First" -msgstr "" +msgstr "Первый" msgid "FirstPushedBy|pushed by" -msgstr "" +msgstr "протолкнул" msgid "Fork" msgid_plural "Forks" @@ -421,22 +425,22 @@ msgstr[1] "Форки" msgstr[2] "Форки" msgid "ForkedFromProjectPath|Forked from" -msgstr "ForkedFromProjectPath|Форк от " +msgstr "Форк от " msgid "From issue creation until deploy to production" -msgstr "" +msgstr "От создания проблемы до развертывания в рабочей среде" msgid "From merge request merge until deploy to production" -msgstr "" +msgstr "От запроса на слияние до развертывания в рабочей среде" msgid "Go to your fork" msgstr "Перейти к вашему форку" msgid "GoToYourFork|Fork" -msgstr "GoToYourFork|Форк" +msgstr "Форк" msgid "Home" -msgstr "Домашняя" +msgstr "Главная" msgid "Housekeeping successfully started" msgstr "Очистка успешно запущена" @@ -448,28 +452,28 @@ msgid "Interval Pattern" msgstr "Шаблон интервала" msgid "Introducing Cycle Analytics" -msgstr "" +msgstr "Внедрение Цикла Аналитик" msgid "Jobs for last month" -msgstr "" +msgstr "Работы за прошлый месяц" msgid "Jobs for last week" -msgstr "" +msgstr "Работы за прошлую неделю" msgid "Jobs for last year" -msgstr "" +msgstr "Работы за прошлый год" msgid "LFSStatus|Disabled" -msgstr "LFSStatus|Отключено" +msgstr "Отключено" msgid "LFSStatus|Enabled" -msgstr "LFSStatus|Включено" +msgstr "Включено" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Последний %d день" +msgstr[1] "Последние %d дни" +msgstr[2] "Последние %d дни" msgid "Last Pipeline" msgstr "Последний конвейер" @@ -494,21 +498,21 @@ msgstr "Покинуть проект" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Ограничение %d события" +msgstr[1] "Ограничение %d событий" +msgstr[2] "Ограничение %d событий" msgid "Median" -msgstr "Медиана" +msgstr "Среднее" msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "MissingSSHKeyWarningLink|добавить ключ SSH" +msgstr "добавить ключ SSH" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "Новое обращение" -msgstr[1] "Новые обращения" -msgstr[2] "Новые обращения" +msgstr[0] "Обращение" +msgstr[1] "Обращения" +msgstr[2] "Обращения" msgid "New Pipeline Schedule" msgstr "Новое расписание конвейера" @@ -535,7 +539,7 @@ msgid "New snippet" msgstr "Новый сниппет" msgid "New tag" -msgstr "Новый тэг" +msgstr "Новый тег" msgid "No repository" msgstr "Нет репозитория" @@ -544,70 +548,70 @@ msgid "No schedules" msgstr "Нет расписания" msgid "Not available" -msgstr "" +msgstr "Недоступно" msgid "Not enough data" -msgstr "" +msgstr "Нет данных" msgid "Notification events" msgstr "Уведомления о событиях" msgid "NotificationEvent|Close issue" -msgstr "NotificationEvent|Обращение закрыто" +msgstr "Обращение закрыто" msgid "NotificationEvent|Close merge request" msgstr "Запрос на объединение закрыт" msgid "NotificationEvent|Failed pipeline" -msgstr "NotificationEvent|Неудача в конвейере" +msgstr "Неудача в конвейере" msgid "NotificationEvent|Merge merge request" -msgstr "NotificationEvent|Объединить запрос на слияние" +msgstr "Объединить запрос на слияние" msgid "NotificationEvent|New issue" -msgstr "NotificationEvent|Новое обращение" +msgstr "Новое обращение" msgid "NotificationEvent|New merge request" -msgstr "NotificationEvent|Новый запрос на слияние" +msgstr "Новый запрос на слияние" msgid "NotificationEvent|New note" -msgstr "NotificationEvent|Новая заметка" +msgstr "Новая заметка" msgid "NotificationEvent|Reassign issue" -msgstr "NotificationEvent|Переназначить обращение" +msgstr "Переназначить обращение" msgid "NotificationEvent|Reassign merge request" -msgstr "NotificationEvent|Переназначить запрос на слияние" +msgstr "Переназначить запрос на слияние" msgid "NotificationEvent|Reopen issue" -msgstr "NotificationEvent|Переоткрыть обращение" +msgstr "Переоткрыть обращение" msgid "NotificationEvent|Successful pipeline" -msgstr "NotificationEvent|Успешно в конвейере" +msgstr "Успешно в конвейере" msgid "NotificationLevel|Custom" -msgstr "NotificationLevel|Настраиваемый" +msgstr "Настраиваемый" msgid "NotificationLevel|Disabled" -msgstr "NotificationLevel|Отключено" +msgstr "Отключено" msgid "NotificationLevel|Global" -msgstr "NotificationLevel|Глобальный" +msgstr "Глобальный" msgid "NotificationLevel|On mention" -msgstr "NotificationLevel|С упоминанием" +msgstr "Упоминание" msgid "NotificationLevel|Participate" -msgstr "NotificationLevel|По участию" +msgstr "Участие" msgid "NotificationLevel|Watch" -msgstr "NotificationLevel|Отслеживать" +msgstr "Отслеживать" msgid "OfSearchInADropdown|Filter" -msgstr "OfSearchInADropdown|Фильтр" +msgstr "Фильтр" msgid "OpenedNDaysAgo|Opened" -msgstr "OpenedNDaysAgo|Открыто" +msgstr "Открыто" msgid "Options" msgstr "Настройки" @@ -619,7 +623,7 @@ msgid "Pipeline" msgstr "Конвейер" msgid "Pipeline Health" -msgstr "" +msgstr "Жизненный цикл конвейера" msgid "Pipeline Schedule" msgstr "Расписание конвейера" @@ -628,67 +632,79 @@ msgid "Pipeline Schedules" msgstr "Расписания конвейеров" msgid "PipelineCharts|Failed:" -msgstr "" +msgstr "Неудача:" msgid "PipelineCharts|Overall statistics" -msgstr "" +msgstr "Статистика" msgid "PipelineCharts|Success ratio:" -msgstr "" +msgstr "Коэффициент успеха:" msgid "PipelineCharts|Successful:" -msgstr "" +msgstr "Успех:" msgid "PipelineCharts|Total:" -msgstr "" +msgstr "Всего:" msgid "PipelineSchedules|Activated" -msgstr "PipelineSchedules|Активировано" +msgstr "Активировано" msgid "PipelineSchedules|Active" -msgstr "PipelineSchedules|Активно" +msgstr "Активно" msgid "PipelineSchedules|All" -msgstr "PipelineSchedules|Все" +msgstr "Все" msgid "PipelineSchedules|Inactive" -msgstr "PipelineSchedules|Неактивно" +msgstr "Неактивно" + +msgid "PipelineSchedules|Input variable key" +msgstr "Ввод ключевой переменной" + +msgid "PipelineSchedules|Input variable value" +msgstr "Вставить значение" msgid "PipelineSchedules|Next Run" -msgstr "PipelineSchedules|Следующий запуск" +msgstr "Следующий запуск" msgid "PipelineSchedules|None" -msgstr "PipelineSchedules|None" +msgstr "Отсутствует" msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "PipelineSchedules|Предоставьте краткое описание этого конвейера" +msgstr "Предоставьте краткое описание этого конвейера" + +msgid "PipelineSchedules|Remove variable row" +msgstr "Удалить значение" msgid "PipelineSchedules|Take ownership" -msgstr "PipelineSchedules|Стать владельцем" +msgstr "Стать владельцем" msgid "PipelineSchedules|Target" -msgstr "PipelineSchedules|Цель" +msgstr "Цель" + +msgid "PipelineSchedules|Variables" +msgstr "Переменные" msgid "PipelineSheduleIntervalPattern|Custom" -msgstr "PipelineSheduleIntervalPattern|Настраиваемый" +msgstr "Настраиваемый" msgid "Pipelines" -msgstr "" +msgstr "Конвейер" msgid "Pipelines charts" -msgstr "" +msgstr "Диаграмма конвейера" msgid "Pipeline|all" -msgstr "" +msgstr "все" msgid "Pipeline|success" -msgstr "" +msgstr "успех" msgid "Pipeline|with stage" -msgstr "Pipeline|со стадией" +msgstr "со стадией" msgid "Pipeline|with stages" -msgstr "Pipeline|со стадиями" +msgstr "со стадиями" msgid "Project '%{project_name}' queued for deletion." msgstr "Проект '%{project_name}' добавлен в очередь на удаление." @@ -724,52 +740,52 @@ msgstr "" "почте." msgid "Project home" -msgstr "Домашняя страница проекта" +msgstr "Домашняя страница" msgid "ProjectFeature|Disabled" -msgstr "ProjectFeature|Отключено" +msgstr "Отключено" msgid "ProjectFeature|Everyone with access" -msgstr "ProjectFeature|Все с доступом" +msgstr "Все с доступом" msgid "ProjectFeature|Only team members" -msgstr "ProjectFeature|Только члены команды" +msgstr "Только члены команды" msgid "ProjectFileTree|Name" -msgstr "ProjectFileTree|Имя" +msgstr "Наименование" msgid "ProjectLastActivity|Never" -msgstr "ProjectLastActivity|Никогда" +msgstr "Никогда" msgid "ProjectLifecycle|Stage" -msgstr "" +msgstr "Этап" msgid "ProjectNetworkGraph|Graph" -msgstr "ProjectNetworkGraph|Граф" +msgstr "Граф" msgid "Read more" -msgstr "" +msgstr "Подробнее" msgid "Readme" msgstr "Readme" msgid "RefSwitcher|Branches" -msgstr "RefSwitcher|Ветки" +msgstr "Ветки" msgid "RefSwitcher|Tags" -msgstr "RefSwitcher|Тэги" +msgstr "Теги" msgid "Related Commits" -msgstr "" +msgstr "Связанные коммиты" msgid "Related Deployed Jobs" -msgstr "" +msgstr "Связанные задачи выгрузки" msgid "Related Issues" -msgstr "" +msgstr "Связанные вопросы" msgid "Related Jobs" -msgstr "" +msgstr "Связанные задачи" msgid "Related Merge Requests" msgstr "Связанные запросы на слияние" @@ -802,7 +818,7 @@ msgid "Scheduling Pipelines" msgstr "Планирование конвейеров" msgid "Search branches and tags" -msgstr "Найти ветки и тэги" +msgstr "Найти ветки и теги" msgid "Select Archive Format" msgstr "Выбрать формат архива" @@ -828,46 +844,49 @@ msgid "Set up auto deploy" msgstr "Настройка автоматического развертывания" msgid "SetPasswordToCloneLink|set a password" -msgstr "SetPasswordToCloneLink|установить пароль" +msgstr "установить пароль" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Показано %d событие" +msgstr[1] "Показано %d событий" +msgstr[2] "Показано %d событий" msgid "Source code" msgstr "Исходный код" msgid "StarProject|Star" -msgstr "StarProject|Отметить" +msgstr "Отметить" msgid "Start a %{new_merge_request} with these changes" msgstr "Начать %{new_merge_request} с этих изменений" msgid "Switch branch/tag" -msgstr "Переключить ветка/тэг" +msgstr "Переключить ветка/тег" msgid "Tag" msgid_plural "Tags" -msgstr[0] "Тэг" -msgstr[1] "Тэги" -msgstr[2] "Тэги" +msgstr[0] "Тег" +msgstr[1] "теги" +msgstr[2] "Теги" msgid "Tags" -msgstr "Тэги" +msgstr "Теги" msgid "Target Branch" -msgstr "Целевая ветка" +msgstr "Ветка" msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " "first merge request." msgstr "" +"На этапе написания кода показывает время первого коммита до создания запроса " +"на слияние. Данные автоматически добавятся после того, как вы создать свой " +"первый запрос на слияние." msgid "The collection of events added to the data gathered for that stage." -msgstr "" +msgstr "Коллекция событий добавленных в данные собранные для этого этапа." msgid "The fork relationship has been removed." msgstr "Связь форка удалена." @@ -890,7 +909,7 @@ msgid "" "project access based on their associated user." msgstr "" "Расписание конвейеров запускает в будущем неоднократно конвейеры, для " -"определенных ветвей или тэгов. Запланированные конвейеры наследуют " +"определенных ветвей или тегов. Запланированные конвейеры наследуют " "ограничения на доступ к проекту на основе связанного с ними пользователя." msgid "" @@ -898,12 +917,18 @@ msgid "" "first commit. This time will be added automatically once you push your first " "commit." msgstr "" +"На этапе планирования показывает время от предыдущего шага до проталкивания " +"первого коммита. Добавляется автоматически, как только проталкиваете свой " +"первый коммит." msgid "" "The production stage shows the total time it takes between creating an issue " "and deploying the code to production. The data will be automatically added " "once you have completed the full idea to production cycle." msgstr "" +"Производственный этап показывает общее время между созданием задачи и " +"развертывание кода в производственной среде. Данные будут автоматически " +"добавлены после полного завершения идеи производственного цикла." msgid "The project can be accessed by any logged in user." msgstr "Доступ к проекту возможен любым зарегистрированным пользователем." @@ -919,27 +944,38 @@ msgid "" "it. The data will automatically be added after you merge your first merge " "request." msgstr "" +"Этап обзора показывает время от создания запроса слияния до его выполнения. " +"Данные будут автоматически добавлены после завершения первого запроса на " +"слияние." msgid "" "The staging stage shows the time between merging the MR and deploying code " "to the production environment. The data will be automatically added once you " "deploy to production for the first time." msgstr "" +"Этап постановки показывает время между слиянием \"MR\" и развертыванием кода " +"в производственной среде. Данные будут автоматически добавлены после " +"развертывания в производстве первый раз." msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " "the related merge request. The data will automatically be added after your " "first pipeline finishes running." msgstr "" +"Этап тестирования показывает время, которое GitLab CI занимает для запуска " +"каждого конвейера для соответствующего запроса на слияние. Данные будут " +"автоматически добавлены после завершения работы вашего первого конвейера." msgid "The time taken by each data entry gathered by that stage." -msgstr "" +msgstr "Время, затраченное каждым элементом, собранным на этом этапе." msgid "" "The value lying at the midpoint of a series of observed values. E.g., " "between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" " 6." msgstr "" +"Среднее значение в ряду. Пример: между 3, 5, 9, среднее 5, между 3, 5, 7, 8, " +"среднее (5+7)/2 = 6." msgid "" "This means you can not push code until you create an empty repository or " @@ -949,139 +985,139 @@ msgstr "" "репозиторий или не импортируете существующий." msgid "Time before an issue gets scheduled" -msgstr "" +msgstr " Время до начала попадания проблемы в планировщик" msgid "Time before an issue starts implementation" -msgstr "" +msgstr "Время до начала работы над проблемой" msgid "Time between merge request creation and merge/close" msgstr "Время между созданием запроса слияния и слиянием / закрытием" msgid "Time until first merge request" -msgstr "" +msgstr "Время до первого запроса на слияние" msgid "Timeago|%s days ago" -msgstr "Timeago|%s дн(я|ей) назад" +msgstr "%s день назад" msgid "Timeago|%s days remaining" -msgstr "Timeago|Осталось %s дн(я|ей)" +msgstr "Осталось %s день" msgid "Timeago|%s hours remaining" -msgstr "Timeago|Осталось %s часов" +msgstr "Осталось %s часов" msgid "Timeago|%s minutes ago" -msgstr "Timeago|%s минут назад" +msgstr "%s минут назад" msgid "Timeago|%s minutes remaining" -msgstr "Timeago|Осталось %s минут(а|ы)" +msgstr "Осталось %s минут" msgid "Timeago|%s months ago" -msgstr "Timeago|%s минут(а|ы) назад" +msgstr "%s минут назад" msgid "Timeago|%s months remaining" -msgstr "Timeago|Осталось %s месяцев(а)" +msgstr "Осталось %s месяц" msgid "Timeago|%s seconds remaining" -msgstr "Timeago|Осталось %s секунд(ы)" +msgstr "Осталось %s секунд(ы)" msgid "Timeago|%s weeks ago" -msgstr "Timeago|%s недель(и) назад" +msgstr "%s недели назад" msgid "Timeago|%s weeks remaining" -msgstr "Timeago|Осталось %s недель(и)" +msgstr "Осталось %s недели" msgid "Timeago|%s years ago" -msgstr "Timeago|%s лет/года назад" +msgstr "%s год назад" msgid "Timeago|%s years remaining" -msgstr "Timeago|Осталось %s лет/года" +msgstr "Осталось %s год" msgid "Timeago|1 day remaining" -msgstr "Timeago|Остался день" +msgstr "Остался день" msgid "Timeago|1 hour remaining" -msgstr "Timeago|Остался час" +msgstr "Остался час" msgid "Timeago|1 minute remaining" -msgstr "Timeago|Осталась одна минута" +msgstr "Осталась одна минута" msgid "Timeago|1 month remaining" -msgstr "Timeago|Остался месяц" +msgstr "Остался месяц" msgid "Timeago|1 week remaining" -msgstr "Timeago|Осталась неделя" +msgstr "Осталась неделя" msgid "Timeago|1 year remaining" -msgstr "Timeago|Остался год" +msgstr "Остался год" msgid "Timeago|Past due" -msgstr "Timeago|Просрочено" +msgstr "Просрочено" msgid "Timeago|a day ago" -msgstr "Timeago|день назад" +msgstr "день назад" msgid "Timeago|a month ago" -msgstr "Timeago|месяц назад" +msgstr "месяц назад" msgid "Timeago|a week ago" -msgstr "Timeago|неделю назад" +msgstr "неделю назад" msgid "Timeago|a while" -msgstr "Timeago|какое-то время" +msgstr "какое-то время" msgid "Timeago|a year ago" -msgstr "Timeago|год назад" +msgstr "год назад" msgid "Timeago|about %s hours ago" -msgstr "Timeago|около %s часов назад" +msgstr "около %s часов назад" msgid "Timeago|about a minute ago" -msgstr "Timeago|около минуты назад" +msgstr "около минуты назад" msgid "Timeago|about an hour ago" -msgstr "Timeago|около часа назад" +msgstr "около часа назад" msgid "Timeago|in %s days" -msgstr "Timeago|через %s дня(ей)" +msgstr "через %s день" msgid "Timeago|in %s hours" -msgstr "Timeago|через %s часа(ов)" +msgstr "через %s час" msgid "Timeago|in %s minutes" -msgstr "Timeago|через %s минут(ы)" +msgstr "через %s минут" msgid "Timeago|in %s months" -msgstr "Timeago|через %s месяц(а|ев)" +msgstr "через %s месяц" msgid "Timeago|in %s seconds" -msgstr "Timeago|через %s секунд(ы)" +msgstr "через %s секунд(ы)" msgid "Timeago|in %s weeks" -msgstr "Timeago|через %s недели" +msgstr "через %s недели" msgid "Timeago|in %s years" -msgstr "Timeago|через %s лет/года" +msgstr "через %s год" msgid "Timeago|in 1 day" -msgstr "Timeago|через день" +msgstr "через день" msgid "Timeago|in 1 hour" -msgstr "Timeago|через час" +msgstr "через час" msgid "Timeago|in 1 minute" -msgstr "Timeago|через минуту" +msgstr "через минуту" msgid "Timeago|in 1 month" -msgstr "Timeago|через месяц" +msgstr "через месяц" msgid "Timeago|in 1 week" -msgstr "Timeago|через неделю" +msgstr "через неделю" msgid "Timeago|in 1 year" -msgstr "Timeago|через год" +msgstr "через год" msgid "Timeago|less than a minute ago" -msgstr "Timeago|менее чем минуту назад" +msgstr "менее чем минуту назад" msgid "Time|hr" msgid_plural "Time|hrs" @@ -1102,19 +1138,19 @@ msgid "Total Time" msgstr "Общее время" msgid "Total test time for all commits/merges" -msgstr "" +msgstr "Общее время тестирования фиксаций/слияний" msgid "Unstar" msgstr "Снять отметку" msgid "Upload New File" -msgstr "Выгрузить новый файл" +msgstr "Загрузить новый файл" msgid "Upload file" -msgstr "Выгрузить файл" +msgstr "Загрузить файл" msgid "UploadLink|click to upload" -msgstr "UploadLink|кликните для выгрузки" +msgstr "кликните для загрузки" msgid "Use your global notification setting" msgstr "Используются глобальный настройки уведомлений" @@ -1123,24 +1159,36 @@ msgid "View open merge request" msgstr "Просмотреть открытый запрос на слияние" msgid "VisibilityLevel|Internal" -msgstr "VisibilityLevel|Ограниченный" +msgstr "Ограниченный" msgid "VisibilityLevel|Private" -msgstr "VisibilityLevel|Приватный" +msgstr "Приватный" msgid "VisibilityLevel|Public" -msgstr "VisibilityLevel|Публичный" +msgstr "Публичный" + +msgid "VisibilityLevel|Unknown" +msgstr "Не определен" msgid "Want to see the data? Please ask an administrator for access." -msgstr "" +msgstr "Хотите увидеть данные? Обратитесь к администратору за доступом." msgid "We don't have enough data to show this stage." -msgstr "" +msgstr "Информация по этапу отсутствует." msgid "Withdraw Access Request" msgstr "Отменить запрос доступа" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Вы собираетесь удалить %{group_name}.\n" +"Удаленные группы НЕ МОГУТ быть восстановлены!\n" +"Вы АБСОЛЮТНО уверены?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 2ded61f40d7..78144d3755d 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -4,12 +4,12 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n" -"PO-Revision-Date: 2017-07-25 03:27-0400\n" +"PO-Revision-Date: 2017-08-06 11:23-0400\n" "Last-Translator: Андрей Витюк <andruwa13@gmail.com>\n" "Language: uk\n" "X-Generator: Zanata 3.9.6\n" @@ -209,9 +209,7 @@ msgstr[1] "Комміта" msgstr[2] "Коммітів" msgid "Commit duration in minutes for last 30 commits" -msgstr "" -"Тривалість коммітів протягом декількох хвилин на протязі 30 останніх " -"коммітів" +msgstr "Тривалість останніх 30 коммітів у хвилинах" msgid "Commit message" msgstr "Комміт повідомлення" @@ -399,7 +397,7 @@ msgid "Failed to remove the pipeline schedule" msgstr "Не вдалося видалити розклад Конвеєра" msgid "Files" -msgstr "Файлів" +msgstr "Файли" msgid "Filter by commit message" msgstr "Фільтрувати повідомлення коммітів" @@ -496,15 +494,15 @@ msgstr "Залишити проект" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Обмеження %d події" +msgstr[1] "Обмеження %d подій" +msgstr[2] "Обмеження %d подій" msgid "Median" msgstr "Медіана" msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "додати SSH ключ" +msgstr "не додасте SSH ключ" msgid "New Issue" msgid_plural "New Issues" @@ -846,9 +844,9 @@ msgstr "встановити пароль" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Показано %d подію" +msgstr[1] "Показано %d події" +msgstr[2] "Показано %d подій" msgid "Source code" msgstr "Код" @@ -879,9 +877,12 @@ msgid "" "request. The data will automatically be added here once you create your " "first merge request." msgstr "" +"На стадії написання коду, показує час першого комміту до створення запиту на " +"об'єднання. Дані будуть автоматично додані після створення вашого першого " +"запиту на об'єднання." msgid "The collection of events added to the data gathered for that stage." -msgstr "" +msgstr "Колекція подій додана до даних, зібраних для цього етапу." msgid "The fork relationship has been removed." msgstr "Зв'язок форка видалена." @@ -912,12 +913,17 @@ msgid "" "first commit. This time will be added automatically once you push your first " "commit." msgstr "" +"На етапі планування відображається час від попереднього кроку до першого " +"комміту. Додається автоматично, як тільки відправится перший комміт." msgid "" "The production stage shows the total time it takes between creating an issue " "and deploying the code to production. The data will be automatically added " "once you have completed the full idea to production cycle." msgstr "" +"Стадія ПРОДакшин показує загальний час між створенням проблеми та " +"розгортанням коду у ПРОДакшині. Дані будуть автоматично додані після " +"завершення повної ідеї до ПРОДакшин циклу." msgid "The project can be accessed by any logged in user." msgstr "Доступ до проекту можливий будь-яким зареєстрованим користувачем." @@ -933,27 +939,37 @@ msgid "" "it. The data will automatically be added after you merge your first merge " "request." msgstr "" +"Стадія огляду показує час від створення запиту про об'єднання до його " +"виконання. Дані будуть автоматично додані після завершення першого запиту на " +"злиття." msgid "" "The staging stage shows the time between merging the MR and deploying code " "to the production environment. The data will be automatically added once you " "deploy to production for the first time." msgstr "" +"Стадія ДЕВ показує час між злиттям \"MR\" та розгортанням коду у ПРОДакшин. " +"Дані автоматично додаються після розгортання у ПРОДакшин вперше." msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " "the related merge request. The data will automatically be added after your " "first pipeline finishes running." msgstr "" +"Стадія тестування показує час, який GitLab CI виконує для запуску кожного " +"конвеєра для відповідного запиту злиття. Дані будуть автоматично додані " +"після завершення першого конвеєра." msgid "The time taken by each data entry gathered by that stage." -msgstr "" +msgstr "Час, витрачений на кожен елемент, зібраний на цьому етапі." msgid "" "The value lying at the midpoint of a series of observed values. E.g., " "between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" " 6." msgstr "" +"Середнє значення в рядку. Приклад: між 3, 5, 9, середніми 5, між 3, 5, 7, 8, " +"середніми (5 + 7) / 2 = 6." msgid "" "This means you can not push code until you create an empty repository or " @@ -963,10 +979,10 @@ msgstr "" "репозиторій або НЕ імпортуєте існуючий." msgid "Time before an issue gets scheduled" -msgstr "" +msgstr "Час до початку потрапляння проблеми в планувальник" msgid "Time before an issue starts implementation" -msgstr "" +msgstr "Час до початку роботи над проблемою" msgid "Time between merge request creation and merge/close" msgstr "Час між створенням запиту злиття і злиттям або закриттям" @@ -1047,7 +1063,7 @@ msgid "Timeago|a year ago" msgstr "рік тому" msgid "Timeago|about %s hours ago" -msgstr "Близько %s годин тому" +msgstr "Близько %s годин(и) тому" msgid "Timeago|about a minute ago" msgstr "Близько хвилини тому" @@ -1145,6 +1161,9 @@ msgstr "Приватний" msgid "VisibilityLevel|Public" msgstr "Публічний" +msgid "VisibilityLevel|Unknown" +msgstr "Невідомий" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Хочете побачити дані? Будь ласка, попросить у адміністратора доступ." @@ -1254,3 +1273,4 @@ msgid_plural "parents" msgstr[0] "джерело" msgstr[1] "джерела" msgstr[2] "джерел" + diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index f471e7def25..4a550db55d2 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -1089,6 +1089,9 @@ msgstr "私有" msgid "VisibilityLevel|Public" msgstr "公开" +msgid "VisibilityLevel|Unknown" +msgstr "未知" + msgid "Want to see the data? Please ask an administrator for access." msgstr "权限不足。如需查看相关数据,请向管理员申请权限。" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 1b7c39f8f62..69b2bf80dbf 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1088,6 +1088,9 @@ msgstr "私有" msgid "VisibilityLevel|Public" msgstr "公開" +msgid "VisibilityLevel|Unknown" +msgstr "未知" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 8d30a78145d..4fd728659c6 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -12,8 +12,8 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n" -"PO-Revision-Date: 2017-07-20 09:50-0400\n" -"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n" +"PO-Revision-Date: 2017-08-07 03:30-0400\n" +"Last-Translator: Huang Tao <htve@outlook.com>\n" "Language: zh-TW\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" @@ -1099,6 +1099,9 @@ msgstr "私有" msgid "VisibilityLevel|Public" msgstr "公開" +msgid "VisibilityLevel|Unknown" +msgstr "不明" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關資料,請向管理員申請權限。" @@ -1120,10 +1123,7 @@ msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" -msgstr "" -"即將要刪除 %{project_name_with_namespace}。\n" -"被刪除的專案完全無法救回來喔!\n" -"真的「100%確定」要這麼做嗎?" +msgstr "即將要刪除 %{project_name_with_namespace}。被刪除的專案完全無法救回來喔!真的「100%確定」要這麼做嗎?" msgid "" "You are going to remove the fork relationship to source project " diff --git a/package.json b/package.json index fd944531a6a..c5247a63e67 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,18 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { + "axios": "^0.16.2", "babel-core": "^6.22.1", "babel-eslint": "^7.2.1", - "babel-loader": "^6.2.10", + "babel-loader": "^7.1.1", "babel-plugin-transform-define": "^1.2.0", "babel-preset-latest": "^6.24.0", "babel-preset-stage-2": "^6.22.0", "bootstrap-sass": "^3.3.6", "compression-webpack-plugin": "^0.3.2", + "copy-webpack-plugin": "^4.0.1", "core-js": "^2.4.1", + "cropper": "^2.3.0", "css-loader": "^0.28.0", "d3": "^3.5.11", "deckar01-task_list": "^2.0.0", @@ -30,6 +33,7 @@ "eslint-plugin-html": "^2.0.1", "exports-loader": "^0.6.4", "file-loader": "^0.11.1", + "imports-loader": "^0.7.1", "jed": "^1.1.1", "jquery": "^2.2.1", "jquery-ujs": "^1.2.1", @@ -37,9 +41,9 @@ "jszip": "^3.1.3", "jszip-utils": "^0.0.2", "marked": "^0.3.6", + "monaco-editor": "0.8.3", "mousetrap": "^1.4.6", "name-all-modules-plugin": "^1.0.1", - "pdfjs-dist": "^1.8.252", "pikaday": "^1.5.1", "prismjs": "^1.6.0", "raphael": "^2.2.7", @@ -48,7 +52,6 @@ "react-dev-utils": "^0.5.2", "select2": "3.5.2-browserify", "sql.js": "^0.4.0", - "stats-webpack-plugin": "^0.4.3", "three": "^0.84.0", "three-orbit-controls": "^82.1.0", "three-stl-loader": "^1.0.4", @@ -60,14 +63,15 @@ "vue-loader": "^11.3.4", "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", - "webpack": "^2.6.1", - "webpack-bundle-analyzer": "^2.8.2" + "webpack": "^3.4.0", + "webpack-bundle-analyzer": "^2.8.2", + "webpack-stats-plugin": "^0.1.5" }, "devDependencies": { "babel-plugin-istanbul": "^4.0.0", "eslint": "^3.10.1", "eslint-config-airbnb-base": "^10.0.1", - "eslint-import-resolver-webpack": "^0.8.1", + "eslint-import-resolver-webpack": "^0.8.3", "eslint-plugin-filenames": "^1.1.0", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jasmine": "^2.1.0", @@ -81,8 +85,8 @@ "karma-jasmine": "^1.1.0", "karma-mocha-reporter": "^2.2.2", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.2", + "karma-webpack": "^2.0.4", "nodemon": "^1.11.0", - "webpack-dev-server": "^2.4.2" + "webpack-dev-server": "^2.6.1" } } diff --git a/rubocop/cop/migration/add_column_with_default_to_large_table.rb b/rubocop/cop/migration/add_column_with_default_to_large_table.rb index 87788b0d9c2..fb363f95b56 100644 --- a/rubocop/cop/migration/add_column_with_default_to_large_table.rb +++ b/rubocop/cop/migration/add_column_with_default_to_large_table.rb @@ -20,6 +20,7 @@ module RuboCop 'necessary'.freeze LARGE_TABLES = %i[ + ci_pipelines ci_builds events issues diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build new file mode 100755 index 00000000000..95d9fe0f176 --- /dev/null +++ b/scripts/gitaly-test-build @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +# This script assumes tmp/tests/gitaly already contains the correct +# Gitaly version. We just have to compile it and run its 'bundle +# install'. We have this separate script for that because weird things +# were happening in CI when we have a 'bundle exec' process that later +# called 'bundle install' using a different Gemfile, as happens with +# gitlab-ce and gitaly. + +dir = 'tmp/tests/gitaly' + +abort 'gitaly build failed' unless system('make', chdir: dir) + +# Make the 'gitaly' executable look newer than 'GITALY_SERVER_VERSION'. +# Without this a gitaly executable created in the setup-test-env job +# will look stale compared to GITALY_SERVER_VERSION. +FileUtils.touch(File.join(dir, 'gitaly'), mtime: Time.now + (1 << 24)) diff --git a/scripts/gitaly-test-spawn b/scripts/gitaly-test-spawn new file mode 100755 index 00000000000..dd603eec7f6 --- /dev/null +++ b/scripts/gitaly-test-spawn @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +gitaly_dir = 'tmp/tests/gitaly' +args = %W[#{gitaly_dir}/gitaly #{gitaly_dir}/config.toml] + +# Print the PID of the spawned process +puts spawn(*args, [:out, :err] => 'log/gitaly-test.log') diff --git a/scripts/lint-conflicts.sh b/scripts/lint-conflicts.sh new file mode 100755 index 00000000000..f3877600c8c --- /dev/null +++ b/scripts/lint-conflicts.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +output=`git ls-files -z | grep -zvE '\.(rb|js|haml)$' | xargs -0n1 grep -HEn '^<<<<<<< '` +echo $output +test -z "$output" diff --git a/scripts/static-analysis b/scripts/static-analysis index 6d35684b97f..e4f80e8fc6f 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -11,7 +11,8 @@ tasks = [ %w[bundle exec rake brakeman], %w[bundle exec license_finder], %w[yarn run eslint], - %w[bundle exec rubocop --require rubocop-rspec] + %w[bundle exec rubocop --require rubocop-rspec], + %w[scripts/lint-conflicts.sh] ] failed_tasks = tasks.reduce({}) do |failures, task| diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index ddf38967dd7..0dad95e418f 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Admin::GroupsController do let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let(:admin) { create(:admin) } before do diff --git a/spec/controllers/admin/health_check_controller_spec.rb b/spec/controllers/admin/health_check_controller_spec.rb new file mode 100644 index 00000000000..0b8e0c8a065 --- /dev/null +++ b/spec/controllers/admin/health_check_controller_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Admin::HealthCheckController, broken_storage: true do + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + describe 'GET show' do + it 'loads the git storage health information' do + get :show + + expect(assigns[:failing_storage_statuses]).not_to be_nil + end + end + + describe 'POST reset_storage_health' do + it 'resets all storage health information' do + expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!) + + post :reset_storage_health + end + end +end diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 2c35d394b74..65587064eb1 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Admin::ProjectsController do - let!(:project) { create(:empty_project, :public) } + let!(:project) { create(:project, :public) } before do sign_in(create(:admin)) diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 4ca0cfc74e9..249bd948847 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -8,7 +8,7 @@ describe Admin::ServicesController do end describe 'GET #edit' do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } Service.available_services_names.each do |service_name| context "#{service_name}" do @@ -27,7 +27,7 @@ describe Admin::ServicesController do end describe "#update" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:service) do RedmineService.create( project: project, diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 69928a906c6..3d21b695af4 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -9,7 +9,7 @@ describe Admin::UsersController do end describe 'DELETE #user with projects' do - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let!(:issue) { create(:issue, author: user) } before do @@ -127,7 +127,7 @@ describe Admin::UsersController do describe 'POST create' do it 'creates the user' do - expect{ post :create, user: attributes_for(:user) }.to change{ User.count }.by(1) + expect { post :create, user: attributes_for(:user) }.to change { User.count }.by(1) end it 'shows only one error message for an invalid email' do diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 1641bddea11..331903a5543 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -108,6 +108,30 @@ describe ApplicationController do end end + describe 'rescue from Gitlab::Git::Storage::Inaccessible' do + controller(described_class) do + def index + raise Gitlab::Git::Storage::Inaccessible.new('broken', 100) + end + end + + it 'renders a 503 when storage is not available' do + sign_in(create(:user)) + + get :index + + expect(response.status).to eq(503) + end + + it 'renders includes a Retry-After header' do + sign_in(create(:user)) + + get :index + + expect(response.headers['Retry-After']).to eq(100) + end + end + describe 'response format' do controller(described_class) do def index diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 58486f33229..3c396e36b24 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe AutocompleteController do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user) } context 'GET users' do diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb index 2b63933008f..a3bfb2f3a87 100644 --- a/spec/controllers/dashboard/labels_controller_spec.rb +++ b/spec/controllers/dashboard/labels_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Dashboard::LabelsController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let!(:label) { create(:label, project: project) } @@ -11,7 +11,7 @@ describe Dashboard::LabelsController do end describe "#index" do - let!(:unrelated_label) { create(:label, project: create(:empty_project, :public)) } + let!(:unrelated_label) { create(:label, project: create(:project, :public)) } it 'returns global labels for projects the user has a relationship with' do get :index, format: :json diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 424f39fd3b8..2dcb67d50f4 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Dashboard::MilestonesController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:project_milestone) { create(:milestone, project: project) } let(:milestone) do diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 4a48621abe1..c8c6b9f41bf 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Dashboard::TodosController do let(:user) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:todo_service) { TodoService.new } before do @@ -14,7 +14,7 @@ describe Dashboard::TodosController do describe 'GET #index' do context 'project authorization' do it 'renders 404 when user does not have read access on given project' do - unauthorized_project = create(:empty_project, :private) + unauthorized_project = create(:project, :private) get :index, project_id: unauthorized_project.id @@ -34,7 +34,7 @@ describe Dashboard::TodosController do end it 'renders 200 when user has access on given project' do - authorized_project = create(:empty_project, :public) + authorized_project = create(:project, :public) get :index, project_id: authorized_project.id diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index 9dceeca168d..2845f258f6f 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Explore::ProjectsController do describe 'GET #trending' do context 'sorting by update date' do - let(:project1) { create(:empty_project, :public, updated_at: 3.days.ago) } - let(:project2) { create(:empty_project, :public, updated_at: 1.day.ago) } + let(:project1) { create(:project, :public, updated_at: 3.days.ago) } + let(:project2) { create(:project, :public, updated_at: 1.day.ago) } before do create(:trending_project, project: project1) diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index aad67dd0164..fbbc67f3ae0 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Groups::MilestonesController do let(:group) { create(:group) } - let!(:project) { create(:empty_project, group: group) } - let!(:project2) { create(:empty_project, group: group) } + let!(:project) { create(:project, group: group) } + let!(:project2) { create(:project, group: group) } let(:user) { create(:user) } let(:title) { '肯定不是中文的问题' } let(:milestone) do diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index c4092303a67..c2ada8c8df7 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe GroupsController do let(:user) { create(:user) } let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let!(:group_member) { create(:group_member, group: group, user: user) } describe 'GET #index' do diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 8ef10dabd4c..e8707760a5a 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -52,7 +52,7 @@ describe Import::BitbucketController do end it "assigns variables" do - @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id) + @project = create(:project, import_type: 'bitbucket', creator_id: user.id) allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status @@ -63,7 +63,7 @@ describe Import::BitbucketController do end it "does not show already added project" do - @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index fffbc805335..5f0f6dea821 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -16,7 +16,7 @@ describe Import::FogbugzController do end it 'assigns variables' do - @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id) + @project = create(:project, import_type: 'fogbugz', creator_id: user.id) stub_client(repos: [@repo]) get :status @@ -26,7 +26,7 @@ describe Import::FogbugzController do end it 'does not show already added project' do - @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') + @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo]) get :status diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 997107dadea..faf1e6f63ea 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -36,7 +36,7 @@ describe Import::GitlabController do end it "assigns variables" do - @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id) + @project = create(:project, import_type: 'gitlab', creator_id: user.id) stub_client(projects: [@repo]) get :status @@ -46,7 +46,7 @@ describe Import::GitlabController do end it "does not show already added project" do - @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') stub_client(projects: [@repo]) get :status diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb index c96fb90f70e..4241db6e771 100644 --- a/spec/controllers/import/google_code_controller_spec.rb +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -27,7 +27,7 @@ describe Import::GoogleCodeController do end it "assigns variables" do - @project = create(:empty_project, import_type: 'google_code', creator_id: user.id) + @project = create(:project, import_type: 'google_code', creator_id: user.id) stub_client(repos: [@repo], incompatible_repos: []) get :status @@ -38,7 +38,7 @@ describe Import::GoogleCodeController do end it "does not show already added project" do - @project = create(:empty_project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') + @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo], incompatible_repos: []) get :status diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index e478a253b3f..e00403118a0 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -24,7 +24,7 @@ describe InvitesController do describe 'GET #decline' do it 'declines user' do get :decline, id: token - expect{member.reload}.to raise_error ActiveRecord::RecordNotFound + expect {member.reload}.to raise_error ActiveRecord::RecordNotFound expect(response).to have_http_status(302) expect(flash[:notice]).to include 'You have declined the invitation to join' diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 6b690407ce3..bef815ee1f7 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe NotificationSettingsController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:group) { create(:group, :internal) } let(:user) { create(:user) } @@ -99,7 +99,7 @@ describe NotificationSettingsController do end context 'not authorized' do - let(:private_project) { create(:empty_project, :private) } + let(:private_project) { create(:project, :private) } before do sign_in(user) diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index 8b71d6518bb..f5ea097af8b 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::AvatarsController do - let(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 02bbc48dc59..64b9af7b845 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -35,6 +35,26 @@ describe Projects::BlobController do end end + context 'with file path and JSON format' do + context "valid branch, valid file" do + let(:id) { 'master/README.md' } + + before do + get(:show, + namespace_id: project.namespace, + project_id: project, + id: id, + format: :json) + end + + it do + expect(response).to be_ok + expect(json_response).to have_key 'html' + expect(json_response).to have_key 'raw_path' + end + end + end + context 'with tree path' do before do get(:show, @@ -48,7 +68,7 @@ describe Projects::BlobController do let(:id) { 'markdown/doc' } it 'redirects' do expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc") + .to redirect_to("/#{project.full_path}/tree/markdown/doc") end end end @@ -193,7 +213,7 @@ describe Projects::BlobController do context "when user doesn't have access" do before do - other_project = create(:empty_project) + other_project = create(:project, :repository) merge_request.update!(source_project: other_project, target_project: other_project) end diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index dc3b72c6de4..3f6c1092163 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::Boards::IssuesController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 0f2664262e8..65beec16307 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::Boards::ListsController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index aed3a45c413..9e2e9a39481 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BoardsController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 9cd4e9dbf84..745d051a5c1 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -33,7 +33,7 @@ describe Projects::BranchesController do let(:ref) { "master" } it 'redirects' do expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/merge_branch") + .to redirect_to("/#{project.full_path}/tree/merge_branch") end end @@ -42,7 +42,7 @@ describe Projects::BranchesController do let(:ref) { "master" } it 'redirects' do expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") + .to redirect_to("/#{project.full_path}/tree/alert('merge');") end end @@ -82,7 +82,7 @@ describe Projects::BranchesController do issue_iid: issue.iid expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch") + .to redirect_to("/#{project.full_path}/tree/1-feature-branch") end it 'posts a system note' do @@ -96,7 +96,7 @@ describe Projects::BranchesController do end context 'repository-less project' do - let(:project) { create :empty_project } + let(:project) { create :project } it 'redirects to newly created branch' do result = { status: :success, branch: double(name: branch) } diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb index efe1a78415b..c3208357694 100644 --- a/spec/controllers/projects/deploy_keys_controller_spec.rb +++ b/spec/controllers/projects/deploy_keys_controller_spec.rb @@ -24,8 +24,8 @@ describe Projects::DeployKeysController do end context 'when json requested' do - let(:project2) { create(:empty_project, :internal)} - let(:project_private) { create(:empty_project, :private)} + let(:project2) { create(:project, :internal)} + let(:project_private) { create(:project, :private)} let(:deploy_key_internal) do create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com') diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 0dbfcf97f6f..3daff1eeea3 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -4,7 +4,7 @@ describe Projects::DeploymentsController do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:environment) { create(:environment, name: 'production', project: project) } before do diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index f88f50c3cc6..5a95f4f6199 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::EnvironmentsController do set(:user) { create(:user) } - set(:project) { create(:empty_project) } + set(:project) { create(:project) } set(:environment) do create(:environment, name: 'production', project: project) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index e0de62e4454..5af03ae118c 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -24,37 +24,4 @@ describe Projects::GraphsController do expect(response).to redirect_to action: :charts end end - - describe 'GET charts' do - let(:linguist_repository) do - double(languages: { - 'Ruby' => 1000, - 'CoffeeScript' => 350, - 'NSIS' => 15 - }) - end - - let(:expected_values) do - nsis_color = "##{Digest::SHA256.hexdigest('NSIS')[0...6]}" - [ - # colors from Linguist: - { label: "Ruby", color: "#701516", highlight: "#701516" }, - { label: "CoffeeScript", color: "#244776", highlight: "#244776" }, - # colors from SHA256 fallback: - { label: "NSIS", color: nsis_color, highlight: nsis_color } - ] - end - - before do - allow(Linguist::Repository).to receive(:new).and_return(linguist_repository) - end - - it 'sets the correct colour according to language' do - get(:charts, namespace_id: project.namespace, project_id: project, id: 'master') - - expected_values.each do |val| - expect(assigns(:languages)).to include(a_hash_including(val)) - end - end - end end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 019a50882ab..f8c792cd0f0 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::GroupLinksController do let(:group) { create(:group, :private) } let(:group2) { create(:group, :private) } - let(:project) { create(:empty_project, :private, group: group2) } + let(:project) { create(:project, :private, group: group2) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index b93ab220f4d..07174660f46 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::HooksController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 9be61342616..2a5ec6d584b 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::ImportsController do describe 'GET #show' do context 'when repository does not exists' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do sign_in(user) diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 2c57f3bcf8d..23601c457b0 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -234,7 +234,7 @@ describe Projects::IssuesController do end context 'when moving issue to another private project' do - let(:another_project) { create(:empty_project, :private) } + let(:another_project) { create(:project, :private) } context 'when user has access to move issue' do before do @@ -292,13 +292,13 @@ describe Projects::IssuesController do it 'rejects an issue recognized as a spam' do expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true) - expect { update_spam_issue }.not_to change{ issue.reload.title } + expect { update_spam_issue }.not_to change { issue.reload.title } end it 'rejects an issue recognized as a spam when recaptcha disabled' do stub_application_setting(recaptcha_enabled: false) - expect { update_spam_issue }.not_to change{ issue.reload.title } + expect { update_spam_issue }.not_to change { issue.reload.title } end it 'creates a spam log' do @@ -358,7 +358,7 @@ describe Projects::IssuesController do end it 'accepts an issue after recaptcha is verified' do - expect{ update_verified_issue }.to change{ issue.reload.title }.to(spammy_title) + expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title) end it 'marks spam log as recaptcha_verified' do @@ -594,7 +594,7 @@ describe Projects::IssuesController do describe 'POST #create' do def post_new_issue(issue_attrs = {}, additional_params = {}) sign_in(user) - project = create(:empty_project, :public) + project = create(:project, :public) project.team << [user, :developer] post :create, { @@ -817,7 +817,7 @@ describe Projects::IssuesController do context "when the user is owner" do let(:owner) { create(:user) } let(:namespace) { create(:namespace, owner: owner) } - let(:project) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } before do sign_in(owner) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 5a295ae47a6..fdd7e6f173f 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::JobsController do include ApiHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:user) { create(:user) } diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index f19ad4c2c81..f4e2dca883d 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::LabelsController do let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let(:user) { create(:user) } before do @@ -73,7 +73,7 @@ describe Projects::LabelsController do describe 'POST #generate' do context 'personal project' do - let(:personal_project) { create(:empty_project, namespace: user.namespace) } + let(:personal_project) { create(:project, namespace: user.namespace) } it 'creates labels' do post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 12e413db902..4eea7041d29 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MattermostsController do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user) } before do diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index 9278ac8edd8..393d38c6e6b 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MergeRequests::ConflictsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index f9d8f0f5fcf..fc4cec53374 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MergeRequests::CreationsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:fork_project) { create(:forked_project_with_submodules) } @@ -83,7 +83,7 @@ describe Projects::MergeRequests::CreationsController do end context 'when the source branch is in a different project to the target' do - let(:other_project) { create(:project) } + let(:other_project) { create(:project, :repository) } before do other_project.team << [user, :master] diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 53fe2bdb189..fad2c8f3ab7 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MergeRequests::DiffsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -36,7 +36,7 @@ describe Projects::MergeRequests::DiffsController do context 'with forked projects with submodules' do render_views - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:fork_project) { create(:forked_project_with_submodules) } let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } @@ -145,7 +145,7 @@ describe Projects::MergeRequests::DiffsController do end context 'when the merge request belongs to a different project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } before do other_project.team << [user, :master] diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 2fce4b7a85f..bb67db268fa 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MergeRequestsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do @@ -106,7 +106,7 @@ describe Projects::MergeRequestsController do end describe 'GET index' do - let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } def get_merge_requests(page = nil) get :index, @@ -150,6 +150,8 @@ describe Projects::MergeRequestsController do context 'when filtering by opened state' do context 'with opened merge requests' do it 'lists those merge requests' do + expect(merge_request).to be_persisted + get_merge_requests expect(assigns(:merge_requests)).to include(merge_request) @@ -191,7 +193,7 @@ describe Projects::MergeRequestsController do end context 'there is no source project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:fork_project) { create(:forked_project_with_submodules) } let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } @@ -429,7 +431,7 @@ describe Projects::MergeRequestsController do context "when the user is owner" do let(:owner) { create(:user) } let(:namespace) { create(:namespace, owner: owner) } - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:project, :repository, namespace: namespace) } before do sign_in owner @@ -587,7 +589,7 @@ describe Projects::MergeRequestsController do describe 'GET ci_environments_status' do context 'the environment is from a forked project' do - let!(:forked) { create(:project) } + let!(:forked) { create(:project, :repository) } let!(:environment) { create(:environment, project: forked) } let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } let(:admin) { create(:admin) } @@ -609,7 +611,7 @@ describe Projects::MergeRequestsController do end it 'links to the environment on that project' do - expect(json_response.first['url']).to match /#{forked.path_with_namespace}/ + expect(json_response.first['url']).to match /#{forked.full_path}/ end end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index bb5a340cd96..62f1fb1f697 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MilestonesController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project, milestone: milestone) } diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 3b88d5b0d7d..f280c55059c 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::NotesController do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:note) { create(:note, noteable: issue, project: project) } @@ -167,10 +167,10 @@ describe Projects::NotesController do end context 'when creating a commit comment from an MR fork' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:fork_project) do - create(:project).tap do |fork| + create(:project, :repository).tap do |fork| create(:forked_project_link, forked_to_project: fork, forked_from_project: project) end end diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index df35d8e86b9..4d0111302f3 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::PagesController do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:request_params) do { diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index 920189be381..ad4d7da3bdd 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::PagesDomainsController do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:pages_domain) { create(:pages_domain, project: project) } let(:request_params) do diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 41bf5580993..4ac0559c679 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::PipelineSchedulesController do include AccessMatchersForController - set(:project) { create(:empty_project, :public) } + set(:project) { create(:project, :public) } let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } describe 'GET #index' do diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index c8de275ca3e..f9d77c7ad03 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -4,7 +4,7 @@ describe Projects::PipelinesController do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:feature) { ProjectFeature::DISABLED } before do @@ -61,7 +61,7 @@ describe Projects::PipelinesController do create_build('post deploy', 3, 'pages 0') end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id) end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 8671d7a78dd..3cb1bec5ea2 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -2,7 +2,7 @@ require('spec_helper') describe Projects::ProjectMembersController do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } describe 'GET index' do it 'should have the project_members address with a 200 status code' do @@ -158,7 +158,7 @@ describe Projects::ProjectMembersController do end context 'and is an owner' do - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } before do project.team << [user, :master] @@ -261,7 +261,7 @@ describe Projects::ProjectMembersController do end describe 'POST apply_import' do - let(:another_project) { create(:empty_project, :private) } + let(:another_project) { create(:project, :private) } let(:member) { create(:user) } before do diff --git a/spec/controllers/projects/prometheus_controller_spec.rb b/spec/controllers/projects/prometheus_controller_spec.rb index eddf7275975..8407a53272a 100644 --- a/spec/controllers/projects/prometheus_controller_spec.rb +++ b/spec/controllers/projects/prometheus_controller_spec.rb @@ -2,7 +2,7 @@ require('spec_helper') describe Projects::PrometheusController do let(:user) { create(:user) } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:prometheus_service) { double('prometheus_service') } diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 464302824a8..2805968dcd9 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::Registry::RepositoriesController do let(:user) { create(:user) } - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } before do sign_in(user) diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb index a823516830e..f4af3587d23 100644 --- a/spec/controllers/projects/registry/tags_controller_spec.rb +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::Registry::TagsController do let(:user) { create(:user) } - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } before do sign_in(user) diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 9c55d159fa0..f712d1e0d63 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::RepositoriesController do describe "GET archive" do context 'as a guest' do it 'responds with redirect in correct format' do - get :archive, namespace_id: project.namespace, project_id: project, format: "zip" + get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master' expect(response.header["Content-Type"]).to start_with('text/html') expect(response).to be_redirect diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb index 0fa249e4405..2b6f988fd9c 100644 --- a/spec/controllers/projects/runners_controller_spec.rb +++ b/spec/controllers/projects/runners_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::RunnersController do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:runner) { create(:ci_runner) } let(:params) do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 5a9d8a75f3e..4e9b0c09ff2 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -28,7 +28,7 @@ describe Projects::ServicesController do context 'success' do context 'with empty project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'with chat notification service' do let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') } diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index e9a91cff1b3..a8f4b79b64c 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -1,7 +1,7 @@ require('spec_helper') describe Projects::Settings::CiCdController do - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index 65f7bb34f4a..e0f9a5b24a6 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::Settings::IntegrationsController do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index c5a4153d991..41d211ed1bb 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -2,7 +2,7 @@ require('spec_helper') describe Projects::TodosController do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } @@ -67,7 +67,7 @@ describe Projects::TodosController do end it "doesn't create todo" do - expect{ go }.not_to change { user.todos.count } + expect { go }.not_to change { user.todos.count } expect(response).to have_http_status(404) end end @@ -135,7 +135,7 @@ describe Projects::TodosController do end it "doesn't create todo" do - expect{ go }.not_to change { user.todos.count } + expect { go }.not_to change { user.todos.count } expect(response).to have_http_status(404) end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 16cd2e076e5..775f3998f5d 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -81,7 +81,7 @@ describe Projects::TreeController do context 'redirect to blob' do let(:id) { 'master/README.md' } it 'redirects' do - redirect_url = "/#{project.path_with_namespace}/blob/master/README.md" + redirect_url = "/#{project.full_path}/blob/master/README.md" expect(subject) .to redirect_to(redirect_url) end @@ -107,7 +107,7 @@ describe Projects::TreeController do it 'redirects to the new directory' do expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/#{branch_name}/#{path}") + .to redirect_to("/#{project.full_path}/tree/#{branch_name}/#{path}") expect(flash[:notice]).to eq('The directory has been successfully created.') end end @@ -118,7 +118,7 @@ describe Projects::TreeController do it 'does not allow overwriting of existing files' do expect(subject) - .to redirect_to("/#{project.path_with_namespace}/tree/master") + .to redirect_to("/#{project.full_path}/tree/master") expect(flash[:alert]).to eq('A file with this name already exists') end end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index cd6961a7bd5..488bcf31371 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -1,7 +1,7 @@ require('spec_helper') describe Projects::UploadsController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index da06fcb7cfb..6957fb43c19 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::VariablesController do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 192cca45d99..8ecd8b6ca71 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,8 +1,8 @@ require('spec_helper') describe ProjectsController do - let(:project) { create(:empty_project) } - let(:public_project) { create(:empty_project, :public) } + let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } let(:user) { create(:user) } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } @@ -34,7 +34,7 @@ describe ProjectsController do end context "user does not have access to project" do - let(:private_project) { create(:empty_project, :private) } + let(:private_project) { create(:project, :private) } it "does not initialize notification setting" do get :show, namespace_id: private_project.namespace, id: private_project @@ -107,6 +107,20 @@ describe ProjectsController do end end + context 'when the storage is not available', broken_storage: true do + let(:project) { create(:project, :broken_storage) } + before do + project.add_developer(user) + sign_in(user) + end + + it 'renders a 503' do + get :show, namespace_id: project.namespace, id: project + + expect(response).to have_http_status(503) + end + end + context "project with empty repo" do let(:empty_project) { create(:project_empty_repo, :public) } @@ -176,7 +190,7 @@ describe ProjectsController do end context "when the url contains .atom" do - let(:public_project_with_dot_atom) { build(:empty_project, :public, name: 'my.atom', path: 'my.atom') } + let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') } it 'expects an error creating the project' do expect(public_project_with_dot_atom).not_to be_valid @@ -185,7 +199,7 @@ describe ProjectsController do context 'when the project is pending deletions' do it 'renders a 404 error' do - project = create(:empty_project, pending_delete: true) + project = create(:project, pending_delete: true) sign_in(user) get :show, namespace_id: project.namespace, id: project @@ -254,7 +268,7 @@ describe ProjectsController do describe '#transfer' do render_views - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:admin) { create(:admin) } let(:new_namespace) { create(:namespace) } @@ -311,8 +325,8 @@ describe ProjectsController do end context "when the project is forked" do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:project, :repository) } + let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:merge_request) do create(:merge_request, source_project: fork_project, @@ -390,7 +404,7 @@ describe ProjectsController do end context 'with forked project' do - let(:project_fork) { create(:project, namespace: user.namespace) } + let(:project_fork) { create(:project, :repository, namespace: user.namespace) } before do create(:forked_project_link, forked_to_project: project_fork) @@ -430,7 +444,7 @@ describe ProjectsController do end describe "GET refs" do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:project, :public, :repository) } it "gets a list of branches and tags" do get :refs, namespace_id: public_project.namespace, id: public_project diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 634563fc290..275181a3d64 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -15,7 +15,7 @@ describe RegistrationsController do it 'signs the user in' do allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) - expect { post(:create, user_params) }.not_to change{ ActionMailer::Base.deliveries.size } + expect { post(:create, user_params) }.not_to change { ActionMailer::Base.deliveries.size } expect(subject.current_user).not_to be_nil end end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index a3708ad0908..37f961d0c94 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -8,7 +8,7 @@ describe SearchController do end it 'finds issue comments' do - project = create(:empty_project, :public) + project = create(:project, :public) note = create(:note_on_issue, project: project) get :show, project_id: project.id, scope: 'notes', search: note.note @@ -23,7 +23,7 @@ describe SearchController do end it "doesn't expose comments on issues" do - project = create(:empty_project, :public, :issues_private) + project = create(:project, :public, :issues_private) note = create(:note_on_issue, project: project) get :show, project_id: project.id, scope: 'notes', search: note.note @@ -33,7 +33,7 @@ describe SearchController do end it "doesn't expose comments on merge_requests" do - project = create(:empty_project, :public, :merge_requests_private) + project = create(:project, :public, :merge_requests_private) note = create(:note_on_merge_request, project: project) get :show, project_id: project.id, scope: 'notes', search: note.note @@ -42,7 +42,7 @@ describe SearchController do end it "doesn't expose comments on snippets" do - project = create(:empty_project, :public, :snippets_private) + project = create(:project, :public, :snippets_private) note = create(:note_on_project_snippet, project: project) get :show, project_id: project.id, scope: 'notes', search: note.note diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 5d2734b8827..31593ce7311 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe SentNotificationsController do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) } let(:issue) do diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb index 1c494b8c7ab..225753333ee 100644 --- a/spec/controllers/snippets/notes_controller_spec.rb +++ b/spec/controllers/snippets/notes_controller_spec.rb @@ -138,7 +138,7 @@ describe Snippets::NotesController do end it "deletes the note" do - expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0) + expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0) end context 'system note' do @@ -147,7 +147,7 @@ describe Snippets::NotesController do end it "does not delete the note" do - expect{ delete :destroy, request_params }.not_to change{ Note.count } + expect { delete :destroy, request_params }.not_to change { Note.count } end end end @@ -166,7 +166,7 @@ describe Snippets::NotesController do end it "does not update the note" do - expect{ delete :destroy, request_params }.not_to change{ Note.count } + expect { delete :destroy, request_params }.not_to change { Note.count } end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 96f719e2b82..b3a40f5d15c 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -131,7 +131,7 @@ describe UploadsController do describe "GET show" do context 'Content-Disposition security measures' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'for PNG files' do it 'returns Content-Disposition: inline' do @@ -203,7 +203,7 @@ describe UploadsController do end context "when viewing a project avatar" do - let!(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } context "when the project is public" do before do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 7aeb6efd86d..a64ad73cba8 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -86,7 +86,7 @@ describe UsersController do end context 'forked project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:forked_project) { Projects::ForkService.new(project, user).execute } before do @@ -104,7 +104,7 @@ describe UsersController do end describe 'GET #calendar_activities' do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 4df9aef2846..1ec042a6ab4 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :board do - project factory: :empty_project + project after(:create) do |board| board.lists.create(list_type: :closed) diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb index a716da46ac6..564fef6833b 100644 --- a/spec/factories/ci/pipeline_schedule.rb +++ b/spec/factories/ci/pipeline_schedule.rb @@ -5,7 +5,7 @@ FactoryGirl.define do ref 'master' active true description "pipeline schedule" - project factory: :empty_project + project trait :nightly do cron '0 1 * * *' diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 35803f0c37f..e83a0e599a8 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -5,7 +5,7 @@ FactoryGirl.define do sha '97de212e80737a608d939f648d959671fb0a0142' status 'pending' - project factory: :empty_project + project factory :ci_pipeline_without_jobs do after(:build) do |pipeline| diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb index 33a17cf7ed5..fa76d0ecd8c 100644 --- a/spec/factories/ci/runner_projects.rb +++ b/spec/factories/ci/runner_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :ci_runner_project, class: Ci::RunnerProject do runner factory: :ci_runner - project factory: :empty_project + project end end diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index f83366136fd..d8fd513ffcf 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -7,6 +7,6 @@ FactoryGirl.define do protected true end - project factory: :empty_project + project end end diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index 89e260cf65b..f4f12a095fc 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -3,7 +3,7 @@ require_relative '../support/repo_helpers' FactoryGirl.define do factory :commit do git_commit RepoHelpers.sample_commit - project factory: :empty_project + project initialize_with do new(git_commit, project) diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb index a5412629195..3806c43ba15 100644 --- a/spec/factories/conversational_development_index_metrics.rb +++ b/spec/factories/conversational_development_index_metrics.rb @@ -2,32 +2,42 @@ FactoryGirl.define do factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do leader_issues 9.256 instance_issues 1.234 + percentage_issues 13.331 leader_notes 30.33333 instance_notes 28.123 + percentage_notes 92.713 leader_milestones 16.2456 instance_milestones 1.234 + percentage_milestones 7.595 leader_boards 5.2123 instance_boards 3.254 + percentage_boards 62.429 leader_merge_requests 1.2 instance_merge_requests 0.6 + percentage_merge_requests 50.0 leader_ci_pipelines 12.1234 instance_ci_pipelines 2.344 + percentage_ci_pipelines 19.334 leader_environments 3.3333 instance_environments 2.2222 + percentage_environments 66.672 leader_deployments 1.200 instance_deployments 0.771 + percentage_deployments 64.25 leader_projects_prometheus_active 0.111 instance_projects_prometheus_active 0.109 + percentage_projects_prometheus_active 98.198 leader_service_desk_issues 15.891 instance_service_desk_issues 13.345 + percentage_service_desk_issues 83.978 end end diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb index 75f8982ecd9..27cece487bd 100644 --- a/spec/factories/deploy_keys_projects.rb +++ b/spec/factories/deploy_keys_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :deploy_keys_project do deploy_key - project factory: :empty_project + project end end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index d8d699fb3aa..9034476d094 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -2,12 +2,10 @@ FactoryGirl.define do factory :environment, class: Environment do sequence(:name) { |n| "environment#{n}" } - project factory: :empty_project + association :project, :repository sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" } trait :with_review_app do |environment| - project - transient do ref 'master' end diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 55727d6b62c..11d2016955c 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :event do - project factory: :empty_project + project author factory: :user trait(:created) { action Event::CREATED } diff --git a/spec/factories/file_uploaders.rb b/spec/factories/file_uploaders.rb index d397dd705a5..622571390d2 100644 --- a/spec/factories/file_uploaders.rb +++ b/spec/factories/file_uploaders.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :file_uploader do skip_create - project factory: :empty_project + project secret nil transient do diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index 66b0f248959..9b34651a4ae 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :forked_project_link do - association :forked_to_project, factory: :project - association :forked_from_project, factory: :project + association :forked_to_project, factory: [:project, :repository] + association :forked_from_project, factory: [:project, :repository] after(:create) do |link| link.forked_from_project.reload @@ -9,7 +9,7 @@ FactoryGirl.define do end trait :forked_to_empty_project do - association :forked_to_project, factory: :empty_project + association :forked_to_project, factory: [:project, :repository] end end end diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index a16695cb7c1..7c3b80198f9 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :issue do title { generate(:title) } author - project factory: :empty_project + project trait :confidential do confidential true diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb index f25939d2d3e..7430466fc57 100644 --- a/spec/factories/label_priorities.rb +++ b/spec/factories/label_priorities.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :label_priority do - project factory: :empty_project + project label sequence(:priority) end diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 22c2a1f15e2..416317d677b 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -5,7 +5,7 @@ FactoryGirl.define do end factory :label, traits: [:base_label], class: ProjectLabel do - project factory: :empty_project + project transient do priority nil diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index b5e2ec60072..2f75bf12cd7 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -27,7 +27,7 @@ FactoryGirl.define do elsif evaluator.project_id milestone.project_id = evaluator.project_id else - milestone.project = create(:empty_project) + milestone.project = create(:project) end end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 046974dcd6e..f0d05504b7e 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -4,7 +4,7 @@ include ActionDispatch::TestProcess FactoryGirl.define do factory :note do - project factory: :empty_project + project note { generate(:title) } author on_issue diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb index b5e96d18b8f..e9171528d86 100644 --- a/spec/factories/notification_settings.rb +++ b/spec/factories/notification_settings.rb @@ -1,8 +1,7 @@ FactoryGirl.define do factory :notification_setting do - source factory: :empty_project + source factory: :project user level 3 - events [] end end diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb index 50341d943f5..e73cc05f9d7 100644 --- a/spec/factories/project_group_links.rb +++ b/spec/factories/project_group_links.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :project_group_link do - project factory: :empty_project + project group end end diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index d754e980931..accae636a3a 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :project_hook do url { generate(:url) } enable_ssl_verification false - project factory: :empty_project + project trait :token do token { SecureRandom.hex(10) } diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index fe4518caadf..9cf3a1e8e8a 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :project_member do user - project factory: :empty_project + project master trait(:guest) { access_level ProjectMember::GUEST } diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb index ae222d5e69a..38fcab7466d 100644 --- a/spec/factories/project_wikis.rb +++ b/spec/factories/project_wikis.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :project_wiki do skip_create - project factory: :empty_project + project user factory: :user initialize_with { new(project, user) } end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 485ed48d2de..3f8e7030b1c 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -5,7 +5,7 @@ FactoryGirl.define do # # Project does not have bare repository. # Use this factory if you don't need repository in tests - factory :empty_project, class: 'Project' do + factory :project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } namespace @@ -54,8 +54,48 @@ FactoryGirl.define do avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) } end + trait :broken_storage do + after(:create) do |project| + project.update_column(:repository_storage, 'broken') + end + end + + # Test repository - https://gitlab.com/gitlab-org/gitlab-test trait :repository do - # no-op... for now! + path { 'gitlabhq' } + + test_repo + + transient do + create_template nil + end + + after :create do |project, evaluator| + if evaluator.create_template + args = evaluator.create_template + + project.add_user(args[:user], args[:access]) + + project.repository.create_file( + args[:user], + ".gitlab/#{args[:path]}/bug.md", + 'something valid', + message: 'test 3', + branch_name: 'master') + project.repository.create_file( + args[:user], + ".gitlab/#{args[:path]}/template_test.md", + 'template_test', + message: 'test 1', + branch_name: 'master') + project.repository.create_file( + args[:user], + ".gitlab/#{args[:path]}/feature_proposal.md", + 'feature_proposal', + message: 'test 2', + branch_name: 'master') + end + end end trait :empty_repo do @@ -64,7 +104,7 @@ FactoryGirl.define do # We delete hooks so that gitlab-shell will not try to authenticate with # an API that isn't running - FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.path_with_namespace}.git", 'hooks')) + FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.disk_path}.git", 'hooks')) end end @@ -72,7 +112,7 @@ FactoryGirl.define do after(:create) do |project| raise "Failed to create repository!" unless project.create_repository - FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.path_with_namespace}.git", 'refs')) + FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.disk_path}.git", 'refs')) end end @@ -146,59 +186,18 @@ FactoryGirl.define do # # This is a case when you just created a project # but not pushed any code there yet - factory :project_empty_repo, parent: :empty_project do + factory :project_empty_repo, parent: :project do empty_repo end # Project with broken repository # # Project with an invalid repository state - factory :project_broken_repo, parent: :empty_project do + factory :project_broken_repo, parent: :project do broken_repo end - # Project with test repository - # - # Test repository source can be found at - # https://gitlab.com/gitlab-org/gitlab-test - factory :project, parent: :empty_project do - path { 'gitlabhq' } - - test_repo - - transient do - create_template nil - end - - after :create do |project, evaluator| - if evaluator.create_template - args = evaluator.create_template - - project.add_user(args[:user], args[:access]) - - project.repository.create_file( - args[:user], - ".gitlab/#{args[:path]}/bug.md", - 'something valid', - message: 'test 3', - branch_name: 'master') - project.repository.create_file( - args[:user], - ".gitlab/#{args[:path]}/template_test.md", - 'template_test', - message: 'test 1', - branch_name: 'master') - project.repository.create_file( - args[:user], - ".gitlab/#{args[:path]}/feature_proposal.md", - 'feature_proposal', - message: 'test 2', - branch_name: 'master') - end - end - end - - factory :forked_project_with_submodules, parent: :empty_project do + factory :forked_project_with_submodules, parent: :project do path { 'forked-gitlabhq' } after :create do |project| @@ -228,11 +227,11 @@ FactoryGirl.define do jira_service end - factory :kubernetes_project, parent: :empty_project do + factory :kubernetes_project, parent: :project do kubernetes_service end - factory :prometheus_project, parent: :empty_project do + factory :prometheus_project, parent: :project do after :create do |project| project.create_prometheus_service( active: true, diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb index 3dbace4b38a..fe0cbfc4444 100644 --- a/spec/factories/protected_branches.rb +++ b/spec/factories/protected_branches.rb @@ -57,5 +57,11 @@ FactoryGirl.define do protected_branch.merge_access_levels.new(access_level: Gitlab::Access::MASTER) end end + + trait :no_one_can_merge do + after(:create) do |protected_branch| + protected_branch.merge_access_levels.first.update!(access_level: Gitlab::Access::NO_ACCESS) + end + end end end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 6a6d6fa171f..74497dc82c0 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -2,6 +2,6 @@ FactoryGirl.define do factory :release do tag "v1.1.0" description "Awesome release" - project factory: :empty_project + project end end diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb index 99253be5a22..c2febf5b438 100644 --- a/spec/factories/sent_notifications.rb +++ b/spec/factories/sent_notifications.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :sent_notification do - project factory: :empty_project + project recipient factory: :user noteable { create(:issue, project: project) } reply_key { SentNotification.reply_key } diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 30bc25cf88a..c2674ce2d11 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -1,11 +1,11 @@ FactoryGirl.define do factory :service do - project factory: :empty_project + project type 'Service' end factory :custom_issue_tracker_service, class: CustomIssueTrackerService do - project factory: :empty_project + project type 'CustomIssueTrackerService' category 'issue_tracker' active true @@ -17,7 +17,7 @@ FactoryGirl.define do end factory :kubernetes_service do - project factory: :empty_project + project active true properties({ api_url: 'https://kubernetes.example.com', @@ -26,7 +26,7 @@ FactoryGirl.define do end factory :prometheus_service do - project factory: :empty_project + project active true properties({ api_url: 'https://prometheus.example.com/' @@ -34,7 +34,7 @@ FactoryGirl.define do end factory :jira_service do - project factory: :empty_project + project active true properties( url: 'https://jira.example.com', @@ -43,7 +43,7 @@ FactoryGirl.define do end factory :hipchat_service do - project factory: :empty_project + project type 'HipchatService' token 'test_token' end diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb index f6ce99d50f9..075bccd7f94 100644 --- a/spec/factories/snippets.rb +++ b/spec/factories/snippets.rb @@ -20,7 +20,7 @@ FactoryGirl.define do end factory :project_snippet, parent: :snippet, class: :ProjectSnippet do - project factory: :empty_project + project end factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb index b11b0a0a17b..1ae7fc9f384 100644 --- a/spec/factories/subscriptions.rb +++ b/spec/factories/subscriptions.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :subscription do user - project factory: :empty_project + project subscribable factory: :issue end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index c1ac3bb84ad..4975befbfe3 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :todo do - project factory: :empty_project + project author user target factory: :issue @@ -45,7 +45,7 @@ FactoryGirl.define do end factory :on_commit_todo, class: Todo do - project factory: :empty_project + project author user action { Todo::ASSIGNED } diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index 931f4ec3d24..9ea3cfa72c6 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' feature 'Admin disables Git access protocol' do include StubENV - let(:project) { create(:empty_project, :empty_repo) } + let(:project) { create(:project, :empty_repo) } let(:admin) { create(:admin) } background do diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index 2e1bfcdcec3..3768727d8ae 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -165,7 +165,7 @@ feature 'Admin Groups' do describe 'shared projects' do it 'renders shared project' do - empty_project = create(:empty_project) + empty_project = create(:project) empty_project.project_group_links.create!( group_access: Gitlab::Access::MASTER, group: group diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index 106e7370a98..37fd3e171eb 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature "Admin Health Check" do +feature "Admin Health Check", feature: true, broken_storage: true do include StubENV before do @@ -55,4 +55,26 @@ feature "Admin Health Check" do expect(page).to have_content('The server is on fire') end end + + context 'with repository storage failures' do + before do + # Track a failure + Gitlab::Git::Storage::CircuitBreaker.for_storage('broken').perform { nil } rescue nil + visit admin_health_check_path + end + + it 'shows storage failure information' do + hostname = Gitlab::Environment.hostname + + expect(page).to have_content('broken: failed storage access attempt on host:') + expect(page).to have_content("#{hostname}: 1 of 10 failures.") + end + + it 'allows resetting storage failures' do + click_button 'Reset git storage health information' + + expect(page).to have_content('Git storage health information has been reset') + expect(page).not_to have_content('failed storage access attempt') + end + end end diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb index 7910d5fb72b..710822ac042 100644 --- a/spec/features/admin/admin_hook_logs_spec.rb +++ b/spec/features/admin/admin_hook_logs_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Admin::HookLogs' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:system_hook) { create(:system_hook) } let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 141109fd464..30fcb334b60 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Admin::Hooks' do before do - @project = create(:empty_project) + @project = create(:project) sign_in(create(:admin)) @system_hook = create(:system_hook) diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 4f69eafcb9d..77710f80036 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -4,7 +4,7 @@ describe "Admin::Projects" do include Select2Helper let(:user) { create :user } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:current_user) { create(:admin) } before do @@ -12,7 +12,7 @@ describe "Admin::Projects" do end describe "GET /admin/projects" do - let!(:archived_project) { create :empty_project, :public, :archived } + let!(:archived_project) { create :project, :public, :archived } before do expect(project).to be_persisted diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 46bab3763cc..e3bb16af38a 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -65,8 +65,8 @@ describe "Admin Runners" do let(:runner) { FactoryGirl.create :ci_runner } before do - @project1 = FactoryGirl.create(:empty_project) - @project2 = FactoryGirl.create(:empty_project) + @project1 = FactoryGirl.create(:project) + @project2 = FactoryGirl.create(:project) visit admin_runner_path(runner) end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index c1eced417cf..c9591a7d854 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -69,6 +69,14 @@ feature 'Admin updates settings' do expect(find('#service_push_channel').value).to eq '#test_channel' end + context 'sign-in restrictions', :js do + it 'de-activates oauth sign-in source' do + find('.btn', text: 'GitLab.com').click + + expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active') + end + end + def check_all_events page.check('Active') page.check('Push') diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 0dde8bb696c..e2e2b13cf8a 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -257,7 +257,7 @@ describe "Admin::Users" do describe "GET /admin/users/:id/projects" do let(:group) { create(:group) } - let!(:project) { create(:empty_project, group: group) } + let!(:project) { create(:project, group: group) } before do group.add_developer(user) diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 5b3ee6ee822..c2b7543a690 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -9,7 +9,7 @@ feature 'Admin uses repository checks' do end scenario 'to trigger a single check' do - project = create(:empty_project) + project = create(:project) visit_admin_project_page(project) page.within('.repository-check') do @@ -20,7 +20,7 @@ feature 'Admin uses repository checks' do end scenario 'to see a single failed repository check' do - project = create(:empty_project) + project = create(:project) project.update_columns( last_repository_check_failed: true, last_repository_check_at: Time.now diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index d70da7f09e9..5aae2dbaf91 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -4,8 +4,8 @@ describe "Dashboard Issues Feed" do describe "GET /issues" do let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } - let!(:project1) { create(:empty_project) } - let!(:project2) { create(:empty_project) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project) } before do project1.team << [user, :master] diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index a7c12853981..321c8a2a670 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -19,7 +19,7 @@ describe "Dashboard Feed" do end context 'feed content' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project, author: user, description: '') } let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) } diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 59e20d7e24d..3eeb4d35131 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -5,7 +5,7 @@ describe 'Issues Feed' do let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } let!(:group) { create(:group) } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) } before do diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index c87469696da..a6ad5981f8f 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'Issue Boards add issue modal', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:planning) { create(:label, project: project, name: 'Planning') } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c3711c9b2c5..ce458431c55 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -4,7 +4,7 @@ describe 'Issue Boards', js: true do include DragTo let(:group) { create(:group, :nested) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:user2) { create(:user) } @@ -233,7 +233,7 @@ describe 'Issue Boards', js: true do wait_for_board_cards(4, 1) expect(find('.board:nth-child(3)')).to have_content(issue6.title) - expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title) + expect(find('.board:nth-child(3)').all('.card').last).to have_content(development.title) end it 'issue moves between lists' do @@ -244,7 +244,7 @@ describe 'Issue Boards', js: true do wait_for_board_cards(4, 1) expect(find('.board:nth-child(2)')).to have_content(issue7.title) - expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title) + expect(find('.board:nth-child(2)').all('.card').first).to have_content(planning.title) end it 'issue moves from closed' do diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index f4be56a4463..4cbb48e2e6e 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Issue Boards', :js do include DragTo - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project) } diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 415eda0e058..61b53aa5d1e 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'Issue Boards shortcut', js: true do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do create(:board, project: project) diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 1c8b9c46569..422d96175f7 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'Issue Boards add issue modal filtering', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:board) { create(:board, project: project) } let(:planning) { create(:label, project: project, name: 'Planning') } let!(:list1) { create(:list, board: board, label: planning, position: 0) } diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 1dbe3dbda11..f67372337ec 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'Issue Boards new issue', js: true do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:board) { create(:board, project: project) } let!(:list) { create(:list, board: board, position: 0) } let(:user) { create(:user) } diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 9b3fb48046a..b3e418bc0f7 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Issue Boards', js: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let!(:milestone) { create(:milestone, project: project) } let!(:development) { create(:label, project: project, name: 'Development') } let!(:bug) { create(:label, project: project, name: 'Bug') } @@ -257,7 +257,7 @@ describe 'Issue Boards', js: true do end end - expect(card).to have_selector('.label', count: 2) + expect(card).to have_selector('.label', count: 3) expect(card).to have_content(bug.title) end @@ -283,7 +283,7 @@ describe 'Issue Boards', js: true do end end - expect(card).to have_selector('.label', count: 3) + expect(card).to have_selector('.label', count: 4) expect(card).to have_content(bug.title) expect(card).to have_content(regression.title) end @@ -308,7 +308,7 @@ describe 'Issue Boards', js: true do end end - expect(card).not_to have_selector('.label') + expect(card).to have_selector('.label', count: 1) expect(card).not_to have_content(stretch.title) end end diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index f54f2234203..11a54079f4f 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Sub-group project issue boards', :js do let(:group) { create(:group) } let(:nested_group_1) { create(:group, parent: group) } - let(:project) { create(:empty_project, group: nested_group_1) } + let(:project) { create(:project, group: nested_group_1) } let(:board) { create(:board, project: project) } let(:label) { create(:label, project: project) } let(:user) { create(:user) } diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 1e7fd7b62bd..64fbc80cb81 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Contributions Calendar', :js do let(:user) { create(:user) } - let(:contributed_project) { create(:empty_project, :public) } + let(:contributed_project) { create(:project, :public) } let(:issue_note) { create(:note, project: contributed_project) } # Ex/ Sunday Jan 1, 2016 diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 15ec6f20763..0c9fcc60d30 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -282,7 +282,7 @@ describe 'Commits' do end # verified and the gpg user has a gitlab profile - click_on 'Verified' + click_on 'Verified', match: :first within '.popover' do expect(page).to have_content 'This commit was signed with a verified signature.' expect(page).to have_content 'Nannie Bernhard' @@ -295,7 +295,7 @@ describe 'Commits' do visit project_commits_path(project, :'signed-commits') - click_on 'Verified' + click_on 'Verified', match: :first within '.popover' do expect(page).to have_content 'This commit was signed with a verified signature.' expect(page).to have_content 'Nannie Bernhard' diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 8f59ce3d2e7..ae39ba4da6b 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe "Container Registry" do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:container_repository) do create(:container_repository, name: 'my/image') diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index a96270c9147..4917dfcf1d1 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -17,7 +17,7 @@ feature 'Dashboard > Activity' do end context 'event filters', :js do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:merge_request) do create(:merge_request, author: user, source_project: project, target_project: project) diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb index ceac6a0a27c..814ec0e59c7 100644 --- a/spec/features/dashboard/archived_projects_spec.rb +++ b/spec/features/dashboard/archived_projects_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Archived Project' do let(:user) { create :user } let(:project) { create :project} - let(:archived_project) { create(:empty_project, :archived) } + let(:archived_project) { create(:project, :archived) } before do project.team << [user, :master] diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 05dcdd93f37..b6dce1b8ec4 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Tooltips on .timeago dates', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:created_date) { Date.yesterday.to_time } let(:expected_format) { created_date.in_time_zone.strftime('%b %-d, %Y %l:%M%P') } diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index ae68b0f65d5..b431f72fcc9 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index 0ce642f32f2..facb67ae787 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -4,7 +4,7 @@ feature 'Dashboard Issues filtering', :js do include SortingHelper let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:milestone) { create(:milestone, project: project) } let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index c53b8bdc002..8b0efa2cfe7 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Dashboard Issues' do let(:current_user) { create :user } let(:user) { current_user } # Shared examples depend on this being available - let!(:public_project) { create(:empty_project, :public) } - let(:project) { create(:empty_project) } - let(:project_with_issues_disabled) { create(:empty_project, :issues_disabled) } + let!(:public_project) { create(:project, :public) } + let(:project) { create(:project) } + let(:project_with_issues_disabled) { create(:project, :issues_disabled) } let!(:authored_issue) { create :issue, author: current_user, project: project } let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project } let!(:assigned_issue) { create :issue, assignees: [current_user], project: project } @@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do end end - it 'shows the new issue page', :js do + it 'shows the new issue page', js: true do find('.new-project-item-select-button').click + wait_for_requests - find('.select2-results li').click - expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new") + project_path = "/#{project.path_with_namespace}" + project_json = { name: project.name_with_namespace, url: project_path }.to_json + + # similate selection, and prevent overlap by dropdown menu + execute_script("$('.project-item-select').val('#{project_json}').trigger('change');") + execute_script("$('#select2-drop-mask').remove();") + + find('.new-project-item-link').click + + expect(page).to have_current_path("#{project_path}/issues/new") page.within('#content-body') do expect(page).to have_selector('.issue-form') diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb index 8e19fb93665..b1a207682c3 100644 --- a/spec/features/dashboard/label_filter_spec.rb +++ b/spec/features/dashboard/label_filter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe 'Dashboard > label filter', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } - let(:project2) { create(:empty_project, name: 'test2', path: 'test2', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) } let(:label) { create(:label, title: 'bug', color: '#ff0000') } let(:label2) { create(:label, title: 'bug') } diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index a406cd89ead..b673fa52ce3 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -5,9 +5,9 @@ feature 'Dashboard Merge Requests' do include SortingHelper let(:current_user) { create :user } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } - let(:public_project) { create(:empty_project, :public, :repository) } + let(:public_project) { create(:project, :public, :repository) } let(:forked_project) { Projects::ForkService.new(public_project, current_user).execute } before do @@ -16,7 +16,7 @@ feature 'Dashboard Merge Requests' do end context 'new merge request dropdown' do - let(:project_with_disabled_merge_requests) { create(:empty_project, :merge_requests_disabled) } + let(:project_with_disabled_merge_requests) { create(:project, :merge_requests_disabled) } before do project_with_disabled_merge_requests.add_master(current_user) diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb index 5ebef1eb097..c965b565ca3 100644 --- a/spec/features/dashboard/milestone_filter_spec.rb +++ b/spec/features/dashboard/milestone_filter_spec.rb @@ -4,7 +4,7 @@ feature 'Dashboard > milestone filter', :js do include FilterItemSelectHelper let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:milestone) { create(:milestone, title: 'v1.0', project: project) } let(:milestone2) { create(:milestone, title: 'v2.0', project: project) } let!(:issue) { create :issue, author: user, project: project, milestone: milestone } diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb index cf32d705365..6fcde35f541 100644 --- a/spec/features/dashboard/milestone_tabs_spec.rb +++ b/spec/features/dashboard/milestone_tabs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Dashboard milestone tabs', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:label) { create(:label, project: project) } let(:project_milestone) { create(:milestone, project: project) } let(:milestone) do diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index 488f7397c69..41d37376cfb 100644 --- a/spec/features/dashboard/milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb @@ -13,7 +13,7 @@ feature 'Dashboard > Milestones' do describe 'as logged-in user' do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let!(:milestone) { create(:milestone, project: project) } before do project.team << [user, :master] diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index f3b538e490e..4a004107408 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Project member activity', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public, name: 'x', namespace: user.namespace) } + let(:project) { create(:project, :public, name: 'x', namespace: user.namespace) } before do project.team << [user, :master] diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index abb9e5eef96..06a43909053 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Dashboard Projects' do let(:user) { create(:user) } - let(:project) { create(:project, name: 'awesome stuff') } + let(:project) { create(:project, :repository, name: 'awesome stuff') } let(:project2) { create(:project, :public, name: 'Community project') } before do diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index c29bcc7c9e9..fb4263d74c4 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Dashboard snippets' do context 'when the project has snippets' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } before do allow(Snippet).to receive(:default_per_page).and_return(1) diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb index 93da36c08fc..030a86d1c01 100644 --- a/spec/features/dashboard/todos/target_state_spec.rb +++ b/spec/features/dashboard/todos/target_state_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' feature 'Dashboard > Todo target states' do let(:user) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } before do sign_in(user) diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index 0a363259fe7..54d477f7274 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -4,8 +4,8 @@ feature 'Dashboard > User filters todos', js: true do let(:user_1) { create(:user, username: 'user_1', name: 'user_1') } let(:user_2) { create(:user, username: 'user_2', name: 'user_2') } - let(:project_1) { create(:empty_project, name: 'project_1') } - let(:project_2) { create(:empty_project, name: 'project_2') } + let(:project_1) { create(:project, name: 'project_1') } + let(:project_2) { create(:project, name: 'project_2') } let(:issue) { create(:issue, title: 'issue', project: project_1) } diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb index d49a78b290f..b7d39a872b0 100644 --- a/spec/features/dashboard/todos/todos_sorting_spec.rb +++ b/spec/features/dashboard/todos/todos_sorting_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Dashboard > User sorts todos' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index e6795c8470f..22e2b3e2eb5 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Dashboard Todos' do let(:user) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, due_date: Date.today) } context 'User does not have todos' do @@ -212,7 +212,7 @@ feature 'Dashboard Todos' do note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project) create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id) - project2 = create(:empty_project, :public) + project2 = create(:project, :public) label2 = create(:label, project: project2) issue2 = create(:issue, project: project2) note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2) diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index a88fe207e0e..c352b6ded14 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe 'Dashboard > User filters projects' do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'Victorialand', namespace: user.namespace) } + let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) } let(:user2) { create(:user) } - let(:project2) { create(:empty_project, name: 'Treasure', namespace: user2.namespace) } + let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) } before do project.team << [user, :master] diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb index fa83ad5d17c..0375d0bf8ff 100644 --- a/spec/features/discussion_comments/commit_spec.rb +++ b/spec/features/discussion_comments/commit_spec.rb @@ -4,7 +4,7 @@ describe 'Discussion Comments Merge Request', :js do include RepoHelpers let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } before do diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb index f52ba9c4d09..9812eaf3420 100644 --- a/spec/features/discussion_comments/issue_spec.rb +++ b/spec/features/discussion_comments/issue_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Discussion Comments Issue', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } before do diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb index 042f39f47e0..b0019c32189 100644 --- a/spec/features/discussion_comments/merge_request_spec.rb +++ b/spec/features/discussion_comments/merge_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Discussion Comments Merge Request', :js do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } before do diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb index 50ba13499d9..1e6389d9a13 100644 --- a/spec/features/discussion_comments/snippets_spec.rb +++ b/spec/features/discussion_comments/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Discussion Comments Issue', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:snippet) { create(:project_snippet, :private, project: project, author: user) } before do diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index 84f41eca999..b5325301968 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -5,7 +5,7 @@ describe 'Explore Groups page', :js do let!(:group) { create(:group) } let!(:public_group) { create(:group, :public) } let!(:private_group) { create(:group, :private) } - let!(:empty_project) { create(:empty_project, group: public_group) } + let!(:empty_project) { create(:project, group: public_group) } before do group.add_owner(user) diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index a4c4d336807..d570b0afe6e 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -4,7 +4,7 @@ feature 'Top Plus Menu', :js do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } - let(:public_project) { create(:empty_project, :public) } + let(:public_project) { create(:project, :public) } before do group.add_owner(user) diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 300296a2b94..53b3bb3b65f 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe "GitLab Flavored Markdown" do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:fred) do create(:user, name: 'fred') do |user| diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 627a930c997..f04e13adba7 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Global search' do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } before do project.team << [user, :master] diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb index e2c7907528b..243e8536168 100644 --- a/spec/features/groups/empty_states_spec.rb +++ b/spec/features/groups/empty_states_spec.rb @@ -9,7 +9,7 @@ feature 'Groups Merge Requests Empty States' do end context 'group has a project' do - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } before do project.add_master(user) @@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do it 'should show a new merge request button' do within '.empty-state' do - expect(page).to have_content('New merge request') + expect(page).to have_content('create merge request') end end @@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do it 'should not show a new merge request button' do within '.empty-state' do - expect(page).not_to have_link('New merge request') + expect(page).not_to have_link('create merge request') end end end diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 121df1ec635..acb21eab03f 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -49,7 +49,7 @@ feature 'Edit group settings' do end context 'with a project' do - given!(:project) { create(:empty_project, group: group) } + given!(:project) { create(:project, group: group) } given(:old_project_full_path) { "/#{group.path}/#{project.path}" } given(:new_project_full_path) { "/#{new_group_path}/#{project.path}" } diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 449a99a2c7b..cdf7aceb13c 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Group issues page' do + include FilteredSearchHelpers + let(:path) { issues_group_path(group) } let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} @@ -31,12 +33,10 @@ feature 'Group issues page' do let(:path) { issues_group_path(group) } it 'filters by only group users' do - click_button('Assignee') - - wait_for_requests + filtered_search.set('assignee:') - expect(find('.dropdown-menu-assignee')).to have_link(user.name) - expect(find('.dropdown-menu-assignee')).not_to have_link(user2.name) + expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) + expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) end end end diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb index 6141981023c..1f3c7fd3859 100644 --- a/spec/features/groups/members/request_access_spec.rb +++ b/spec/features/groups/members/request_access_spec.rb @@ -4,7 +4,7 @@ feature 'Groups > Members > Request access' do let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } - let!(:project) { create(:empty_project, :private, namespace: group) } + let!(:project) { create(:project, :private, namespace: group) } background do group.add_owner(owner) diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb index 0e43eed8699..3df77a104e8 100644 --- a/spec/features/issuables/close_reopen_report_toggle_spec.rb +++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb @@ -42,7 +42,7 @@ describe 'Issuables Close/Reopen/Report toggle' do end context 'on an issue' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issuable) { create(:issue, project: project) } before do @@ -59,7 +59,7 @@ describe 'Issuables Close/Reopen/Report toggle' do end context 'when user doesnt have permission to update' do - let(:cant_project) { create(:empty_project) } + let(:cant_project) { create(:project) } let(:cant_issuable) { create(:issue, project: cant_project) } before do diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb index 7c20c96528e..b72b690110f 100644 --- a/spec/features/issuables/default_sort_order_spec.rb +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Projects > Issuables > Default sort order' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:first_created_issuable) { issuables.order_created_asc.first } let(:last_created_issuable) { issuables.order_created_desc.first } diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 557de721222..2f45ef856a5 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'issuable list' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } issuable_types = [:issue, :merge_request] diff --git a/spec/features/issuables/markdown_references_spec.rb b/spec/features/issuables/markdown_references_spec.rb index 169381d703a..dd4e10a9886 100644 --- a/spec/features/issuables/markdown_references_spec.rb +++ b/spec/features/issuables/markdown_references_spec.rb @@ -2,10 +2,10 @@ require 'rails_helper' describe 'Markdown References', :js do let(:user) { create(:user) } - let(:actual_project) { create(:project, :public) } + let(:actual_project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project)} let(:issue_actual_project) { create(:issue, project: actual_project) } - let!(:other_project) { create(:empty_project, :public) } + let!(:other_project) { create(:project, :public) } let!(:issue_other_project) { create(:issue, project: other_project) } let(:issues) { [issue_actual_project, issue_other_project] } diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 6cb0bf6fdfd..134e618feac 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'Awards Emoji' do - let!(:project) { create(:empty_project, :public) } + let!(:project) { create(:project, :public) } let!(:user) { create(:user) } let(:issue) do create(:issue, diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb index 740281c1050..e95eb19f7d1 100644 --- a/spec/features/issues/award_spec.rb +++ b/spec/features/issues/award_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Issue awards', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } describe 'logged in' do diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index 5acf8fdae84..847e3856ba5 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Issues > Labels bulk assignment' do let(:user) { create(:user) } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:issue1) { create(:issue, project: project, title: "Issue 1") } let!(:issue2) { create(:issue, project: project, title: "Issue 2") } let!(:bug) { create(:label, project: project, title: 'bug') } diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb index f59f687cf51..546dc7e8a49 100644 --- a/spec/features/issues/create_branch_merge_request_spec.rb +++ b/spec/features/issues/create_branch_merge_request_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Create Branch/Merge Request Dropdown on issue page', js: true do +feature 'Create Branch/Merge Request Dropdown on issue page', :feature, :js do let(:user) { create(:user) } let!(:project) { create(:project, :repository) } let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') } @@ -14,10 +14,14 @@ feature 'Create Branch/Merge Request Dropdown on issue page', js: true do it 'allows creating a merge request from the issue page' do visit project_issue_path(project, issue) - select_dropdown_option('create-mr') - - expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"') - expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first)) + perform_enqueued_jobs do + select_dropdown_option('create-mr') + + expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"') + expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first)) + + wait_for_requests + end visit project_issue_path(project, issue) diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index a403d885de0..a69bd8a09b7 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Dropdown assignee', :js do include FilteredSearchHelpers - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user, name: 'administrator', username: 'root') } let!(:user_john) { create(:user, name: 'John', username: 'th0mas') } let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') } diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index b7d9bbd7e1d..4bbf18e1dbe 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Dropdown author', js: true do include FilteredSearchHelpers - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user, name: 'administrator', username: 'root') } let!(:user_john) { create(:user, name: 'John', username: 'th0mas') } let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') } diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 292fd683271..04d6dea4b8c 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Dropdown hint', :js do include FilteredSearchHelpers - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user) } let(:filtered_search) { find('.filtered-search') } let(:js_dropdown_hint) { '#js-dropdown-hint' } diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index e8f005d7752..67eb0ef0119 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Dropdown label', js: true do include FilteredSearchHelpers - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:filtered_search) { find('.filtered-search') } let(:js_dropdown_label) { '#js-dropdown-label' } diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index ace73f4b1a6..456eb05f241 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Dropdown milestone', :js do include FilteredSearchHelpers - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user) } let!(:milestone) { create(:milestone, title: 'v1.0', project: project) } let!(:uppercase_milestone) { create(:milestone, title: 'CAP_MILESTONE', project: project) } diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 265bcb3a8e5..cd2cbf4bfe7 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -5,7 +5,7 @@ describe 'Filter issues', js: true do include FilteredSearchHelpers let!(:group) { create(:group) } - let!(:project) { create(:empty_project, group: group) } + let!(:project) { create(:project, group: group) } let!(:user) { create(:user, username: 'joe', name: 'Joe') } let!(:user2) { create(:user, username: 'jane') } let!(:label) { create(:label, project: project) } diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index 51c0be69abc..4fc1f3209b3 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe 'Recent searches', js: true do include FilteredSearchHelpers - let(:project_1) { create(:empty_project, :public) } - let(:project_2) { create(:empty_project, :public) } + let(:project_1) { create(:project, :public) } + let(:project_2) { create(:project, :public) } let(:project_1_local_storage_key) { "#{project_1.full_path}-issue-recent-searches" } before do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 115875d72ce..aa9d0d842de 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Search bar', js: true do include FilteredSearchHelpers - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user) } let(:filtered_search) { find('.filtered-search') } diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index d00d0a9c81b..52efe944b69 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -4,7 +4,7 @@ describe 'Visual tokens', js: true do include FilteredSearchHelpers include WaitForRequests - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user, name: 'administrator', username: 'root') } let!(:user_rock) { create(:user, name: 'The Rock', username: 'rock') } let!(:milestone_nine) { create(:milestone, title: '9.0', project: project) } diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 0ba02ba42ba..4297bfff3d9 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -4,7 +4,7 @@ describe 'New/edit issue', :js do include ActionView::Helpers::JavaScriptHelper include FormHelper - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:user) { create(:user)} let!(:user2) { create(:user)} let!(:milestone) { create(:milestone, project: project) } @@ -276,7 +276,7 @@ describe 'New/edit issue', :js do describe 'sub-group project' do let(:group) { create(:group) } let(:nested_group_1) { create(:group, parent: group) } - let(:sub_group_project) { create(:empty_project, group: nested_group_1) } + let(:sub_group_project) { create(:project, group: nested_group_1) } before do sub_group_project.add_master(user) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 2f69e299326..2c1ba207ede 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'GFM autocomplete', js: true do let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb index a8ac1d605cb..9c10f78f67a 100644 --- a/spec/features/issues/group_label_sidebar_spec.rb +++ b/spec/features/issues/group_label_sidebar_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Group label on issue' do it 'renders link to the project issues page' do group = create(:group) - project = create(:empty_project, :public, namespace: group) + project = create(:project, :public, namespace: group) feature = create(:group_label, group: group, title: 'feature') issue = create(:labeled_issue, project: project, labels: [feature]) label_link = project_issues_path(project, label_name: [feature.name]) diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index a7a7e02b59c..28b636f9359 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Issue Detail', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, author: user) } context 'when user displays the issue' do diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 50aa5fbb790..396cca6a3e2 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -4,7 +4,7 @@ feature 'Issue Sidebar' do include MobileHelpers let(:group) { create(:group, :nested) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project, title: 'bug') } @@ -130,8 +130,8 @@ feature 'Issue Sidebar' do it 'adds new label' do page.within('.block.labels') do fill_in 'new_label_name', with: 'wontfix' - page.find(".suggest-colors a", match: :first).click - click_button 'Create' + page.find('.suggest-colors a', match: :first).trigger('click') + page.find('button', text: 'Create').trigger('click') page.within('.dropdown-page-one') do expect(page).to have_content 'wontfix' @@ -142,8 +142,8 @@ feature 'Issue Sidebar' do it 'shows error message if label title is taken' do page.within('.block.labels') do fill_in 'new_label_name', with: label.title - page.find('.suggest-colors a', match: :first).click - click_button 'Create' + page.find('.suggest-colors a', match: :first).trigger('click') + page.find('button', text: 'Create').trigger('click') page.within('.dropdown-page-two') do expect(page).to have_content 'Title has already been taken' diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index 0f869970460..8c23fcd483b 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' feature 'Issue markdown toolbar', js: true do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 2ab7d1a71b7..494c309c9ea 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -25,8 +25,8 @@ feature 'issue move to another project' do context 'user has permission to move issue' do let!(:mr) { create(:merge_request, source_project: old_project) } - let(:new_project) { create(:empty_project) } - let(:new_project_search) { create(:empty_project) } + let(:new_project) { create(:project) } + let(:new_project_search) { create(:project) } let(:text) { "Text with #{mr.to_reference}" } let(:cross_reference) { old_project.to_reference(new_project) } @@ -63,8 +63,8 @@ feature 'issue move to another project' do end context 'user does not have permission to move the issue to a project', js: true do - let!(:private_project) { create(:empty_project, :private) } - let(:another_project) { create(:empty_project) } + let!(:private_project) { create(:project, :private) } + let(:another_project) { create(:project) } background { another_project.team << [user, :guest] } scenario 'browsing projects in projects select' do diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb index b524260750e..9f08ecc214b 100644 --- a/spec/features/issues/note_polling_spec.rb +++ b/spec/features/issues/note_polling_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Issue notes polling', :js do include NoteInteractionHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } describe 'creates' do diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb index 29c9b99030a..05c93a19253 100644 --- a/spec/features/issues/notes_on_issues_spec.rb +++ b/spec/features/issues/notes_on_issues_spec.rb @@ -35,21 +35,21 @@ describe 'Create notes on issues', :js do context 'mentioning issue on a private project' do it_behaves_like 'notes with reference' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:mention) { create(:issue, project: project) } end end context 'mentioning issue on an internal project' do it_behaves_like 'notes with reference' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } let(:mention) { create(:issue, project: project) } end end context 'mentioning issue on a public project' do it_behaves_like 'notes with reference' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:mention) { create(:issue, project: project) } end end diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 7a05e8b2ccc..332ce78b138 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'New issue', js: true do include StubENV - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user)} before do diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 2460ae817f9..8405f1cd48d 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' feature 'Manually create a todo item from issue', js: true do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 389151e63f0..5a7c4f54cb6 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' feature 'Multiple issue updating from issues#index', :js do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index d28ad52ff56..4b63cc844f3 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -9,7 +9,7 @@ feature 'Issues > User uses quick actions', js: true do describe 'issue-only commands' do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } before do project.team << [user, :master] diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 4f4bf189280..4259f76963d 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -6,7 +6,7 @@ describe 'Issues' do include SortingHelper let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } before do sign_in(user) @@ -367,7 +367,7 @@ describe 'Issues' do end describe 'when I want to reset my incoming email token' do - let(:project1) { create(:empty_project, namespace: user.namespace) } + let(:project1) { create(:project, namespace: user.namespace) } let!(:issue) { create(:issue, project: project1) } before do @@ -706,4 +706,30 @@ describe 'Issues' do expect(page).to have_text("updated title") end end + + describe 'confidential issue#show', js: true do + it 'shows confidential sibebar information as confidential and can be turned off' do + issue = create(:issue, :confidential, project: project) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.confidential-issue-warning') + expect(page).to have_css('.is-confidential') + expect(page).not_to have_css('.is-not-confidential') + + find('.confidential-edit').click + expect(page).to have_css('.confidential-warning-message') + + within('.confidential-warning-message') do + find('.btn-close').click + end + + wait_for_requests + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.is-confidential') + expect(page).to have_css('.is-not-confidential') + end + end end diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb index 0e97254eada..299b4f5708a 100644 --- a/spec/features/merge_requests/closes_issues_spec.rb +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -26,17 +26,11 @@ feature 'Merge Request closing issues message', js: true do wait_for_requests end - context 'not closing or mentioning any issue' do - it 'does not display closing issue message' do - expect(page).not_to have_css('.mr-widget-footer') - end - end - context 'closing issues but not mentioning any other issue' do let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -44,7 +38,7 @@ feature 'Merge Request closing issues message', js: true do let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") + expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -52,8 +46,8 @@ feature 'Merge Request closing issues message', js: true do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issue #{issue_1.to_reference}.") - expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.") + expect(page).to have_content("Closes #{issue_1.to_reference}") + expect(page).to have_content("Mentions #{issue_2.to_reference}") end end @@ -61,7 +55,7 @@ feature 'Merge Request closing issues message', js: true do let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -69,7 +63,7 @@ feature 'Merge Request closing issues message', js: true do let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") + expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -77,8 +71,8 @@ feature 'Merge Request closing issues message', js: true do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.") - expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.") + expect(page).to have_content("Closes #{issue_1.to_reference}") + expect(page).to have_content("Mentions #{issue_2.to_reference}") end end end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 11a74276898..d7f3d91e625 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -67,8 +67,8 @@ feature 'Create New Merge Request', js: true do visit project_new_merge_request_path(project, merge_request: { target_project_id: private_project.id }) - expect(page).not_to have_content private_project.path_with_namespace - expect(page).to have_content project.path_with_namespace + expect(page).not_to have_content private_project.full_path + expect(page).to have_content project.full_path end end @@ -78,8 +78,8 @@ feature 'Create New Merge Request', js: true do visit project_new_merge_request_path(project, merge_request: { source_project_id: private_project.id }) - expect(page).not_to have_content private_project.path_with_namespace - expect(page).to have_content project.path_with_namespace + expect(page).not_to have_content private_project.full_path + expect(page).to have_content project.full_path end end diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 6ffb05c5030..89410b0e90f 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -41,7 +41,7 @@ describe 'New/edit merge request', :js do expect(page).to have_content user2.name end - click_link 'Assign to me' + find('a', text: 'Assign to me').trigger('click') expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index 574f5fe353e..ac46cc1f0e4 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -41,8 +41,8 @@ feature 'Merge When Pipeline Succeeds', :js do it 'activates the Merge when pipeline succeeds feature' do click_button "Merge when pipeline succeeds" - expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." - expect(page).to have_content "The source branch will not be removed." + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds" + expect(page).to have_content "The source branch will not be removed" expect(page).to have_selector ".js-cancel-auto-merge" visit_merge_request(merge_request) # Needed to refresh the page expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i @@ -97,11 +97,11 @@ feature 'Merge When Pipeline Succeeds', :js do describe 'enabling Merge when pipeline succeeds via dropdown' do it 'activates the Merge when pipeline succeeds feature' do - click_button 'Select merge moment' + find('.js-merge-moment').click click_link 'Merge when pipeline succeeds' - expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." - expect(page).to have_content "The source branch will not be removed." + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds" + expect(page).to have_content "The source branch will not be removed" expect(page).to have_link "Cancel automatic merge" end end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 5c6eec44ff7..59e67420333 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -43,7 +43,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t wait_for_requests expect(page).to have_button 'Merge when pipeline succeeds' - expect(page).not_to have_button 'Select merge moment' + expect(page).not_to have_button '.js-merge-moment' end end @@ -56,7 +56,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t wait_for_requests expect(page).to have_css('button[disabled="disabled"]', text: 'Merge') - expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') + expect(page).to have_content('Please retry the job or push a new commit to fix the failure') end end @@ -69,7 +69,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t wait_for_requests expect(page).not_to have_button 'Merge' - expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') + expect(page).to have_content('Please retry the job or push a new commit to fix the failure') end end @@ -113,7 +113,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t expect(page).to have_button 'Merge when pipeline succeeds' - click_button 'Select merge moment' + page.find('.js-merge-moment').click expect(page).to have_content 'Merge immediately' end end diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb index b3d6cf8deb4..347ce788b36 100644 --- a/spec/features/merge_requests/pipelines_spec.rb +++ b/spec/features/merge_requests/pipelines_spec.rb @@ -1,45 +1,88 @@ require 'spec_helper' feature 'Pipelines for Merge Requests', js: true do - given(:user) { create(:user) } - given(:merge_request) { create(:merge_request) } - given(:project) { merge_request.target_project } + describe 'pipeline tab' do + given(:user) { create(:user) } + given(:merge_request) { create(:merge_request) } + given(:project) { merge_request.target_project } - before do - project.team << [user, :master] - sign_in user - end - - context 'with pipelines' do - let!(:pipeline) do - create(:ci_empty_pipeline, - project: merge_request.source_project, - ref: merge_request.source_branch, - sha: merge_request.diff_head_sha) + before do + project.team << [user, :master] + sign_in user end - before do - visit project_merge_request_path(project, merge_request) + context 'with pipelines' do + let!(:pipeline) do + create(:ci_empty_pipeline, + project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha) + end + + before do + visit project_merge_request_path(project, merge_request) + end + + scenario 'user visits merge request pipelines tab' do + page.within('.merge-request-tabs') do + click_link('Pipelines') + end + wait_for_requests + + expect(page).to have_selector('.stage-cell') + end end - scenario 'user visits merge request pipelines tab' do - page.within('.merge-request-tabs') do - click_link('Pipelines') + context 'without pipelines' do + before do + visit project_merge_request_path(project, merge_request) end - wait_for_requests - expect(page).to have_selector('.stage-cell') + scenario 'user visits merge request page' do + page.within('.merge-request-tabs') do + expect(page).to have_no_link('Pipelines') + end + end end end - context 'without pipelines' do - before do - visit project_merge_request_path(project, merge_request) + describe 'race condition' do + given(:project) { create(:project, :repository) } + given(:user) { create(:user) } + given(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } } + + given(:merge_request_params) do + { "source_branch" => "feature", "source_project_id" => project.id, + "target_branch" => "master", "target_project_id" => project.id, "title" => "A" } + end + + background do + project.add_master(user) + sign_in user end - scenario 'user visits merge request page' do - page.within('.merge-request-tabs') do - expect(page).to have_no_link('Pipelines') + context 'when pipeline and merge request were created simultaneously' do + background do + stub_ci_pipeline_to_return_yaml_file + + threads = [] + + threads << Thread.new do + @merge_request = MergeRequests::CreateService.new(project, user, merge_request_params).execute + end + + threads << Thread.new do + @pipeline = Ci::CreatePipelineService.new(project, user, build_push_data).execute(:push) + end + + threads.each { |thr| thr.join } + end + + scenario 'user sees pipeline in merge request widget' do + visit project_merge_request_path(project, @merge_request) + + expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature']) + expect(page.find(".ci-widget")).to have_content("##{@pipeline.id}") end end end diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 43cab65d287..95c50df1896 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -3,17 +3,17 @@ require 'rails_helper' feature 'Merge Requests > User uses quick actions', js: true do include QuickActionsHelpers - let(:user) { create(:user) } - let(:project) { create(:project, :public, :repository) } - let(:merge_request) { create(:merge_request, source_project: project) } - let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do let(:issuable) { create(:merge_request, source_project: project) } let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } end describe 'merge-request-only commands' do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:merge_request) { create(:merge_request, source_project: project) } + let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } + before do project.team << [user, :master] sign_in(user) diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 69e31c7481f..fd991293ee9 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -219,4 +219,17 @@ describe 'Merge request', :js do expect(page).to have_field('remove-source-branch-input', disabled: true) end end + + context 'ongoing merge process' do + it 'shows Merging state' do + allow_any_instance_of(MergeRequest).to receive(:merge_ongoing?).and_return(true) + + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).not_to have_button('Merge') + expect(page).to have_content('This merge request is in the process of being merged') + end + end end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 1d05184d6fc..6c9dc67ad74 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Milestone' do let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:user) { create(:user) } before do diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb index 199a5ba83b3..20303359c46 100644 --- a/spec/features/milestones/show_spec.rb +++ b/spec/features/milestones/show_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe 'Milestone show' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:milestone) { create(:milestone, project: project) } let(:labels) { create_list(:label, 2, project: project) } let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } } diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index 81b0a2f541b..a22d548eef3 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Member autocomplete', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:author) { create(:user) } let(:note) { create(:note, noteable: noteable, project: noteable.project) } diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb index 5e1e7dc078f..b45972b7f6b 100644 --- a/spec/features/password_reset_spec.rb +++ b/spec/features/password_reset_spec.rb @@ -16,7 +16,7 @@ feature 'Password reset' do user.send_reset_password_instructions user.update_attribute(:reset_password_sent_at, 5.minutes.ago) - expect{ forgot_password(user) }.to change{ user.reset_password_sent_at } + expect { forgot_password(user) }.to change { user.reset_password_sent_at } expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path end @@ -27,7 +27,7 @@ feature 'Password reset' do # Reload because PG handles datetime less precisely than Ruby/Rails user.reload - expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at } + expect { forgot_password(user) }.not_to change { user.reset_password_sent_at } expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path end diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb index 56c1f7ae9c7..9944a6e1ff1 100644 --- a/spec/features/profiles/account_spec.rb +++ b/spec/features/profiles/account_spec.rb @@ -27,7 +27,7 @@ feature 'Profile > Account' do end context 'with a project' do - given!(:project) { create(:empty_project, namespace: user.namespace) } + given!(:project) { create(:project, namespace: user.namespace) } given(:new_project_path) { "/#{new_username}/#{project.path}" } given(:old_project_path) { "/#{user.username}/#{project.path}" } diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index 3b57ff47d33..091fb3a356c 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Profile > Preferences' do +describe 'Profile > Preferences', :js do let(:user) { create(:user) } before do @@ -8,28 +8,32 @@ describe 'Profile > Preferences' do visit profile_preferences_path end - describe 'User changes their syntax highlighting theme', js: true do + describe 'User changes their syntax highlighting theme' do it 'creates a flash message' do choose 'user_color_scheme_id_5' + wait_for_requests + expect_preferences_saved_message end it 'updates their preference' do choose 'user_color_scheme_id_5' - allowing_for_delay do - visit page.current_path - expect(page).to have_checked_field('user_color_scheme_id_5') - end + wait_for_requests + refresh + + expect(page).to have_checked_field('user_color_scheme_id_5') end end - describe 'User changes their default dashboard', js: true do + describe 'User changes their default dashboard' do it 'creates a flash message' do select 'Starred Projects', from: 'user_dashboard' click_button 'Save' + wait_for_requests + expect_preferences_saved_message end @@ -37,12 +41,12 @@ describe 'Profile > Preferences' do select 'Starred Projects', from: 'user_dashboard' click_button 'Save' - allowing_for_delay do - find('#logo').click + wait_for_requests + + find('#logo').click - expect(page).to have_content("You don't have starred projects yet") - expect(page.current_path).to eq starred_dashboard_projects_path - end + expect(page).to have_content("You don't have starred projects yet") + expect(page.current_path).to eq starred_dashboard_projects_path find('.shortcuts-activity').click diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb index c2c17df67fb..e0feed02259 100644 --- a/spec/features/profiles/user_visits_notifications_tab_spec.rb +++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'User visits the notifications tab', js: true do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb index b054f543dc6..84c2faa2015 100644 --- a/spec/features/projects/activity/rss_spec.rb +++ b/spec/features/projects/activity/rss_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Project Activity RSS' do let(:user) { create(:user) } - let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } let(:path) { activity_project_path(project) } before do diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb index f5f7eba8e40..42b47cb3301 100644 --- a/spec/features/projects/artifacts/browse_spec.rb +++ b/spec/features/projects/artifacts/browse_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Browse artifact', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb index c1bba8c15c4..f1bdb2812c6 100644 --- a/spec/features/projects/artifacts/download_spec.rb +++ b/spec/features/projects/artifacts/download_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Download artifact', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) } let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) } diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb index 4c268b876ea..b2be10a7e0c 100644 --- a/spec/features/projects/artifacts/file_spec.rb +++ b/spec/features/projects/artifacts/file_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Artifact file', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb index 128e39e7803..0bec6e9ad31 100644 --- a/spec/features/projects/artifacts/raw_spec.rb +++ b/spec/features/projects/artifacts/raw_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Raw artifact', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 8cf4bfbf978..368a046f741 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'test coverage badge' do given!(:user) { create(:user) } - given!(:project) { create(:empty_project, :private) } + given!(:project) { create(:project, :private) } context 'when user has access to view badge' do background do diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 9855cfd85c4..62ac9fd0e95 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Editing file blob', js: true do include TreeHelper - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } let(:branch) { 'master' } let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] } diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb index 7145e286229..fe8567ce348 100644 --- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' feature 'Developer views empty project instructions' do - let(:project) { create(:empty_project, :empty_repo) } + let(:project) { create(:project, :empty_repo) } let(:developer) { create(:user) } background do diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb index 4b5436027f9..d3b1d1f7be3 100644 --- a/spec/features/projects/edit_spec.rb +++ b/spec/features/projects/edit_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Project edit', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.team << [user, :master] @@ -20,7 +20,7 @@ feature 'Project edit', js: true do end context 'given project with merge_requests_disabled access level' do - let(:project) { create(:empty_project, :merge_requests_disabled) } + let(:project) { create(:project, :merge_requests_disabled) } it 'hides merge requests section' do expect(page).to have_selector('.merge-requests-feature', visible: false) @@ -36,7 +36,7 @@ feature 'Project edit', js: true do end context 'given project with builds_disabled access level' do - let(:project) { create(:empty_project, :builds_disabled) } + let(:project) { create(:project, :builds_disabled) } it 'hides builds select section' do expect(page).to have_selector('.builds-feature', visible: false) diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index c6b7e611a5c..56addd64056 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Environment' do - given(:project) { create(:empty_project) } + given(:project) { create(:project) } given(:user) { create(:user) } given(:role) { :developer } diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 70e6a1ff1cc..bb943b6bc45 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Environments page', :js do - given(:project) { create(:empty_project) } + given(:project) { create(:project) } given(:user) { create(:user) } given(:role) { :developer } diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 37fa61d038e..24691629063 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -20,21 +20,25 @@ describe 'Edit Project Settings' do visit edit_project_path(project) select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level" - click_button 'Save changes' + page.within('.sharing-permissions') do + click_button 'Save changes' + end wait_for_requests expect(page).not_to have_selector(".shortcuts-#{shortcut_name}") select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level" - click_button 'Save changes' + page.within('.sharing-permissions') do + click_button 'Save changes' + end wait_for_requests expect(page).to have_selector(".shortcuts-#{shortcut_name}") select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level" - click_button 'Save changes' + page.within('.sharing-permissions') do + click_button 'Save changes' + end wait_for_requests expect(page).to have_selector(".shortcuts-#{shortcut_name}") - - sleep 0.1 end end end @@ -174,7 +178,11 @@ describe 'Edit Project Settings' do it "disables repository related features" do select "Disabled", from: "project_project_feature_attributes_repository_access_level" - expect(find(".edit-project")).to have_selector("select.disabled", count: 2) + page.within('.sharing-permissions') do + click_button "Save changes" + end + + expect(find(".sharing-permissions")).to have_selector("select.disabled", count: 2) end it "shows empty features project homepage" do @@ -182,7 +190,9 @@ describe 'Edit Project Settings' do select "Disabled", from: "project_project_feature_attributes_issues_access_level" select "Disabled", from: "project_project_feature_attributes_wiki_access_level" - click_button "Save changes" + page.within('.sharing-permissions') do + click_button "Save changes" + end wait_for_requests visit project_path(project) @@ -195,7 +205,9 @@ describe 'Edit Project Settings' do select "Disabled", from: "project_project_feature_attributes_issues_access_level" select "Disabled", from: "project_project_feature_attributes_wiki_access_level" - click_button "Save changes" + page.within('.sharing-permissions') do + click_button "Save changes" + end wait_for_requests visit activity_project_path(project) @@ -236,7 +248,9 @@ describe 'Edit Project Settings' do end def save_changes_and_check_activity_tab - click_button "Save changes" + page.within('.sharing-permissions') do + click_button "Save changes" + end wait_for_requests visit activity_project_path(project) @@ -249,7 +263,7 @@ describe 'Edit Project Settings' do # Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/24056 describe 'project statistic visibility' do - let!(:project) { create(:empty_project, :private) } + let!(:project) { create(:project, :private) } before do project.team << [member, :guest] diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 1f4b3763b40..7bcab01c739 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'project owner sees a link to create a license file in empty project', js: true do let(:project_master) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do project.team << [project_master, :master] sign_in(project_master) diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb index b63e5ae46ee..cff3b1f5743 100644 --- a/spec/features/projects/gfm_autocomplete_load_spec.rb +++ b/spec/features/projects/gfm_autocomplete_load_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'GFM autocomplete loading', js: true do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do sign_in(create(:admin)) diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb index 30800aae468..d468216d28b 100644 --- a/spec/features/projects/group_links_spec.rb +++ b/spec/features/projects/group_links_spec.rb @@ -4,7 +4,7 @@ feature 'Project group links', :js do include Select2Helper let(:master) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:group) { create(:group) } background do @@ -35,7 +35,7 @@ feature 'Project group links', :js do context 'nested group project' do let!(:nested_group) { create(:group, parent: group) } let!(:another_group) { create(:group) } - let!(:project) { create(:empty_project, namespace: nested_group) } + let!(:project) { create(:project, namespace: nested_group) } background do group.add_master(master) diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index 1c5f89fa898..2385e1d9333 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Guest navigation menu' do - let(:project) { create(:empty_project, :private, public_builds: false) } + let(:project) { create(:project, :private, public_builds: false) } let(:guest) { create(:user) } before do diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index f924725870b..24e7843db63 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -29,8 +29,9 @@ feature 'Import/Export - project import integration test', js: true do fill_in :project_path, with: 'test-project-path', visible: true click_link 'GitLab export' - expect(page).to have_content('GitLab project export') + expect(page).to have_content('Import an exported GitLab project') expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path") + expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original attach_file('file', file) @@ -46,7 +47,7 @@ feature 'Import/Export - project import integration test', js: true do end scenario 'invalid project' do - project = create(:empty_project, namespace: namespace) + project = create(:project, namespace: namespace) visit new_project_path @@ -60,17 +61,6 @@ feature 'Import/Export - project import integration test', js: true do expect(page).to have_content('Project could not be imported') end end - - scenario 'project with no name' do - create(:empty_project, namespace: namespace) - - visit new_project_path - - select2(namespace.id, from: '#project_namespace_id') - - # Check for tooltip disabled import button - expect(find('.import_gitlab_project')['title']).to eq('Please enter a valid project name.') - end end context 'when limited to the default user namespace' do diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb index 3917e72c39e..691b0e1e4ca 100644 --- a/spec/features/projects/import_export/namespace_export_file_spec.rb +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -4,7 +4,7 @@ feature 'Import/Export - Namespace export file cleanup', js: true do let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/features/projects/issuable_counts_caching_spec.rb b/spec/features/projects/issuable_counts_caching_spec.rb index 703d1cbd327..1804d9dc244 100644 --- a/spec/features/projects/issuable_counts_caching_spec.rb +++ b/spec/features/projects/issuable_counts_caching_spec.rb @@ -4,7 +4,7 @@ describe 'Issuable counts caching', :use_clean_rails_memory_store_caching do let!(:member) { create(:user) } let!(:member_2) { create(:user) } let!(:non_member) { create(:user) } - let!(:project) { create(:empty_project, :public) } + let!(:project) { create(:project, :public) } let!(:open_issue) { create(:issue, project: project) } let!(:confidential_issue) { create(:issue, :confidential, project: project, author: non_member) } let!(:closed_issue) { create(:issue, :closed, project: project) } diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb index c2ca62508a4..9fc03f49f5b 100644 --- a/spec/features/projects/issues/list_spec.rb +++ b/spec/features/projects/issues/list_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Issues List' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do project.team << [user, :developer] diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb index d274a1760a4..58eeef8c258 100644 --- a/spec/features/projects/issues/rss_spec.rb +++ b/spec/features/projects/issues/rss_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Project Issues RSS' do - let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } let(:path) { project_issues_path(project) } before do diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index 2b0aead440c..0292a3192d8 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Issue prioritization' do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } # Labels let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index 3115a643d5d..5716d151250 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Labels subscription' do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let!(:bug) { create(:label, project: project, title: 'bug') } let!(:feature) { create(:group_label, group: group, title: 'feature') } diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 223f94ff9f9..8f85e972027 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -5,7 +5,7 @@ feature 'Prioritize labels' do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let!(:bug) { create(:label, project: project, title: 'bug') } let!(:wontfix) { create(:label, project: project, title: 'wontfix') } let!(:feature) { create(:group_label, group: group, title: 'feature') } diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb index a26e7becdb9..bf0990d675d 100644 --- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb +++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Anonymous user sees members' do let(:user) { create(:user) } let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } background do project.team << [user, :master] diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index acda5808313..1c348b987d4 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Anonymous user sees members', js: true do let(:user) { create(:user) } let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } background do project.team << [user, :master] diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index 1fb5e000239..6b450fa4e45 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Group member cannot leave group project' do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } background do group.add_developer(user) diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb index 8e3520f9f15..296a80a3c60 100644 --- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Group member cannot request access to his group project' do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } scenario 'owner does not see the request access button' do group.add_owner(user) diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb index 154f9f4a26c..c140fece41d 100644 --- a/spec/features/projects/members/group_members_spec.rb +++ b/spec/features/projects/members/group_members_spec.rb @@ -4,7 +4,7 @@ feature 'Projects members' do let(:user) { create(:user) } let(:developer) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } - let(:project) { create(:empty_project, :public, :access_requestable, creator: user, group: group) } + let(:project) { create(:project, :public, :access_requestable, creator: user, group: group) } let(:project_invitee) { create(:project_member, project: project, invite_token: '123', invite_email: 'test1@abc.com', user: nil) } let(:group_invitee) { create(:group_member, group: group, invite_token: '123', invite_email: 'test2@abc.com', user: nil) } let(:project_requester) { create(:user) } diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb index 6865d721201..c8988aa63a7 100644 --- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb +++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb @@ -4,7 +4,7 @@ feature 'Projects > Members > Group requester cannot request access to project', let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } - let(:project) { create(:empty_project, :public, :access_requestable, namespace: group) } + let(:project) { create(:project, :public, :access_requestable, namespace: group) } background do group.add_owner(owner) diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index f9c54d267b5..237c059e595 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -6,7 +6,7 @@ feature 'Project members list' do let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } background do sign_in(user1) diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index b4381ea373e..cd621b6b3ce 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -5,7 +5,7 @@ feature 'Projects > Members > Master adds member with expiration date', js: true include ActiveSupport::Testing::TimeHelpers let(:master) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:new_member) { create(:user) } background do diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index 0f96a7cc70d..eb3c8034873 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Master manages access requests' do let(:user) { create(:user) } let(:master) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } background do project.request_access(user) diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb index 7f39f5b2513..04806f8fd9e 100644 --- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Projects > Members > Member cannot request access to his project' do let(:member) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do project.team << [member, :developer] diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb index 8b6d0aafcf8..15162d01c44 100644 --- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Projects > Members > Owner cannot leave project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do sign_in(project.owner) diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb index d838af6d976..c27925c8dc4 100644 --- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Projects > Members > Owner cannot request access to his project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } background do sign_in(project.owner) diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index 45c2647e6e2..afa173c59e5 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Sorting' do let(:master) { create(:user, name: 'John Doe') } let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } - let(:project) { create(:empty_project, namespace: master.namespace, creator: master) } + let(:project) { create(:project, namespace: master.namespace, creator: master) } background do create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago) diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb index b1c38ecc9ab..30de3e83fbb 100644 --- a/spec/features/projects/milestones/milestone_spec.rb +++ b/spec/features/projects/milestones/milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Project milestone' do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:milestone) { create(:milestone, project: project) } before do diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb index 4bd1929ac1e..c531b81e04d 100644 --- a/spec/features/projects/milestones/milestones_sorting_spec.rb +++ b/spec/features/projects/milestones/milestones_sorting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Milestones sorting', :js do include SortingHelper let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } before do # Milestones diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb index 7cfcccda439..f7900210fe6 100644 --- a/spec/features/projects/milestones/new_spec.rb +++ b/spec/features/projects/milestones/new_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Creating a new project milestone', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } before do login_as(user) diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 42f23ee5dec..013ed6f2e58 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Pages' do - given(:project) { create(:empty_project) } + given(:project) { create(:project) } given(:user) { create(:user) } given(:role) { :master } diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 605415d2af4..24b335a7068 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -219,6 +219,25 @@ feature 'Pipeline Schedules', :js do end end end + + context 'when active is true and next_run_at is NULL' do + background do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + pipeline_schedule.update_attribute(:cron, nil) # Consequently next_run_at will be nil + end + end + + scenario 'user edit and recover the problematic pipeline schedule' do + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + fill_in 'schedule_cron', with: '* 1 2 3 4' + click_button 'Save pipeline schedule' + + page.within('.pipeline-schedule-table-row:nth-child(1)') do + expect(page).to have_css(".next-run-cell time") + end + end + end end context 'logged in as non-member' do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 45b1d482c8a..59e32e169ec 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Pipeline', :js do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f05d8685cfb..92479558553 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Pipelines', :js do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when user is logged in' do let(:user) { create(:user) } diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 6001bcfff0a..7d4ec2b4e68 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -4,7 +4,7 @@ describe 'Edit Project Settings' do include Select2Helper let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace, path: 'gitlab', name: 'sample') } + let(:project) { create(:project, namespace: user.namespace, path: 'gitlab', name: 'sample') } before do sign_in(user) @@ -14,7 +14,9 @@ describe 'Edit Project Settings' do it 'shows errors for invalid project name' do visit edit_project_path(project) fill_in 'project_name_edit', with: 'foo&bar' - click_button 'Save changes' + page.within('.general-settings') do + click_button 'Save changes' + end expect(page).to have_field 'project_name_edit', with: 'foo&bar' expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." expect(page).to have_button 'Save changes' @@ -23,7 +25,9 @@ describe 'Edit Project Settings' do it 'shows a successful notice when the project is updated' do visit edit_project_path(project) fill_in 'project_name_edit', with: 'hello world' - click_button 'Save changes' + page.within('.general-settings') do + click_button 'Save changes' + end expect(page).to have_content "Project 'hello world' was successfully updated." end end @@ -86,7 +90,7 @@ describe 'Edit Project Settings' do it 'overrides the redirect' do old_path = project_path(project) rename_project(project, path: 'bar') - new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz') + new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz') visit old_path expect(current_path).to eq(old_path) expect(find('h1.title')).to have_content(new_project.name) @@ -132,7 +136,7 @@ describe 'Edit Project Settings' do it 'overrides the redirect' do old_path = project_path(project) transfer_project(project, group) - new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz') + new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz') visit old_path expect(current_path).to eq(old_path) expect(find('h1.title')).to have_content(new_project.name) diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb index 645713d0805..b6df3212f3d 100644 --- a/spec/features/projects/services/jira_service_spec.rb +++ b/spec/features/projects/services/jira_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Setup Jira service', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:service) { project.create_jira_service } let(:url) { 'http://jira.example.com' } diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 134d7e5e8b7..95d5e8b14b9 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Setup Mattermost slash commands', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:service) { project.create_mattermost_slash_commands_service } let(:mattermost_enabled) { true } diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb index 824cae261e0..c10ec5e2987 100644 --- a/spec/features/projects/services/slack_service_spec.rb +++ b/spec/features/projects/services/slack_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Slack service > Setup events' do let(:user) { create(:user) } let(:service) { SlackService.new } - let(:project) { create(:empty_project, slack_service: service) } + let(:project) { create(:project, slack_service: service) } background do service.fields diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb index 6002c589fba..a8baf126269 100644 --- a/spec/features/projects/services/slack_slash_command_spec.rb +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Slack slash commands' do given(:user) { create(:user) } - given(:project) { create(:empty_project) } + given(:project) { create(:project) } given(:service) { project.create_slack_slash_commands_service } background do diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index 1de4918e142..d932c4e4d9a 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Integration settings' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:role) { :developer } let(:integrations_path) { project_settings_integrations_path(project) } diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index 796e2026905..104ce08d9f3 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Project settings > Merge Requests', :js do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } background do @@ -20,6 +20,9 @@ feature 'Project settings > Merge Requests', :js do expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level" + within('.sharing-permissions-form') do + click_on('Save changes') + end expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') @@ -37,6 +40,9 @@ feature 'Project settings > Merge Requests', :js do expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level" + within('.sharing-permissions-form') do + click_on('Save changes') + end expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') @@ -55,6 +61,9 @@ feature 'Project settings > Merge Requests', :js do expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level" + within('.sharing-permissions-form') do + click_on('Save changes') + end expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') @@ -73,7 +82,9 @@ feature 'Project settings > Merge Requests', :js do scenario 'when unchecked sets :printing_merge_request_link_enabled to false' do uncheck('project_printing_merge_request_link_enabled') - click_on('Save') + within('.merge-request-settings-form') do + click_on('Save changes') + end # Wait for save to complete and page to reload checkbox = find_field('project_printing_merge_request_link_enabled') diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index f24d7ff64d0..232d796a200 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature "Pipelines settings" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:role) { :developer } diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index 1e705d211ea..1756c7d00fe 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Visibility settings', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace, visibility_level: 20) } + let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) } context 'as owner' do before do diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb index 2c6d0a56311..bf18c444c3d 100644 --- a/spec/features/projects/shortcuts_spec.rb +++ b/spec/features/projects/shortcuts_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Project shortcuts' do - let(:project) { create(:empty_project, name: 'Victorialand') } + let(:project) { create(:project, name: 'Victorialand') } let(:user) { create(:user) } describe 'On a project', js: true do diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 7f0e7e3116c..3e79dba3f19 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -4,7 +4,7 @@ feature 'Create Snippet', :js do include DropzoneHelper let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } def fill_form fill_in 'project_snippet_title', with: 'My Snippet Title' diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb index 0822684a42c..1cfbbb4cb62 100644 --- a/spec/features/projects/snippets_spec.rb +++ b/spec/features/projects/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Project snippets', :js do context 'when the project has snippets' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } let!(:other_snippet) { create(:project_snippet) } diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb index 262dcc0abff..aaf64d42515 100644 --- a/spec/features/projects/sub_group_issuables_spec.rb +++ b/spec/features/projects/sub_group_issuables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Subgroup Issuables', :js, :nested_groups do let!(:group) { create(:group, name: 'group') } let!(:subgroup) { create(:group, parent: group, name: 'subgroup') } - let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') } + let!(:project) { create(:project, namespace: subgroup, name: 'project') } let(:user) { create(:user) } before do diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index a6c5a486bcc..d38a5b1324b 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -5,7 +5,7 @@ feature 'Download buttons in tags page' do given(:role) { :developer } given(:status) { 'success' } given(:tag) { 'v1.0.0' } - given(:project) { create(:project) } + given(:project) { create(:project, :repository) } given(:pipeline) do create(:ci_pipeline, diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 327bda28dd6..2df7d4aab06 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Projects > Wiki > User previews markdown changes', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let(:wiki_content) do <<-HEREDOC [regular link](regular) @@ -38,10 +38,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>") end end @@ -60,10 +60,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") end end @@ -82,10 +82,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") end end end @@ -115,10 +115,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>") end end @@ -132,10 +132,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") end end @@ -149,10 +149,10 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do expect(page).to have_content("regular link") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") + expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") end end end diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb index 2c668185666..eaff5f876b6 100644 --- a/spec/features/projects/wiki/shortcuts_spec.rb +++ b/spec/features/projects/wiki/shortcuts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Wiki shortcuts', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let(:wiki_page) do WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index c1e28752c99..ada08d594a3 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -11,7 +11,7 @@ feature 'Projects > Wiki > User creates wiki page', :js do end context 'in the user namespace' do - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } context 'when wiki is empty' do before do @@ -157,7 +157,7 @@ feature 'Projects > Wiki > User creates wiki page', :js do end context 'in a group namespace' do - let(:project) { create(:empty_project, namespace: create(:group, :public)) } + let(:project) { create(:project, namespace: create(:group, :public)) } context 'when wiki is empty' do before do diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb index 9c58e336605..9a92622ba2b 100644 --- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Projects > Wiki > User views Git access wiki page' do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:wiki_page) do WikiPages::CreateService.new( project, @@ -20,7 +20,7 @@ describe 'Projects > Wiki > User views Git access wiki page' do visit project_wiki_path(project, wiki_page) click_link 'Clone repository' - expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}") + expect(page).to have_text("Clone repository #{project.wiki.full_path}") expect(page).to have_text(project.wiki.http_url_to_repo) end end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index 8271428582d..64a80aec205 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -12,7 +12,7 @@ feature 'Projects > Wiki > User updates wiki page' do end context 'in the user namespace' do - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } context 'the home page' do scenario 'success when the wiki content is not empty' do @@ -55,7 +55,7 @@ feature 'Projects > Wiki > User updates wiki page' do scenario 'page has been updated since the user opened the edit page' do click_link 'Edit' - wiki_page.update('Update') + wiki_page.update(content: 'Update') click_button 'Save changes' @@ -64,7 +64,7 @@ feature 'Projects > Wiki > User updates wiki page' do end context 'in a group namespace' do - let(:project) { create(:empty_project, namespace: create(:group, :public)) } + let(:project) { create(:project, namespace: create(:group, :public)) } scenario 'the home page' do click_link 'Edit' diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb index 4f94ab1a609..92e96f11219 100644 --- a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Projects > Wiki > User views the wiki page' do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:old_page_version_id) { wiki_page.versions.last.id } let(:wiki_page) do WikiPages::CreateService.new( diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb index 8d1e6f66039..cf9fe4c1ad1 100644 --- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb @@ -10,7 +10,7 @@ describe 'Projects > Wiki > User views wiki in project page' do context 'when repository is disabled for project' do let(:project) do - create(:empty_project, + create(:project, :repository_disabled, :merge_requests_disabled, :builds_disabled) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 3295f7f9174..d3d7915bebf 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,27 @@ require 'spec_helper' feature 'Project' do + describe 'creating from template' do + let(:user) { create(:user) } + let(:template) { Gitlab::ProjectTemplate.find(:rails) } + + before do + sign_in user + visit new_project_path + end + + it "allows creation from templates" do + page.choose(template.name) + fill_in("project_path", with: template.name) + + page.within '#content-body' do + click_button "Create project" + end + + expect(page).to have_content 'This project Loading..' + end + end + describe 'description' do let(:project) { create(:project, :repository) } let(:path) { project_path(project) } @@ -36,7 +57,7 @@ feature 'Project' do describe 'remove forked relationship', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } before do sign_in user @@ -57,7 +78,7 @@ feature 'Project' do describe 'removal', js: true do let(:user) { create(:user, username: 'test', name: 'test') } - let(:project) { create(:empty_project, namespace: user.namespace, name: 'project1') } + let(:project) { create(:project, namespace: user.namespace, name: 'project1') } before do sign_in(user) @@ -76,7 +97,7 @@ feature 'Project' do describe 'project title' do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } before do sign_in(user) @@ -92,8 +113,8 @@ feature 'Project' do describe 'project title' do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } - let(:project2) { create(:empty_project, namespace: user.namespace, path: 'test') } + let(:project) { create(:project, namespace: user.namespace) } + let(:project2) { create(:project, namespace: user.namespace, path: 'test') } let(:issue) { create(:issue, project: project) } context 'on issues page', js: true do @@ -146,6 +167,21 @@ feature 'Project' do end end + describe 'activity view' do + let(:user) { create(:user, project_view: 'activity') } + let(:project) { create(:project, :repository) } + + before do + project.team << [user, :master] + sign_in user + visit project_path(project) + end + + it 'loads activity', :js do + expect(page).to have_selector('.event-item') + end + end + def remove_with_confirm(button_text, confirm_with) click_button button_text fill_in 'confirm_name_input', with: confirm_with diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb index 1074eb62b33..3bf25221e36 100644 --- a/spec/features/reportable_note/commit_spec.rb +++ b/spec/features/reportable_note/commit_spec.rb @@ -4,7 +4,7 @@ describe 'Reportable note on commit', :js do include RepoHelpers let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.add_master(user) diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb index 9964a32db2e..21e96f6f103 100644 --- a/spec/features/reportable_note/issue_spec.rb +++ b/spec/features/reportable_note/issue_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Reportable note on issue', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let!(:note) { create(:note_on_issue, noteable: issue, project: project) } diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb index a491abdb6cb..bb296546e06 100644 --- a/spec/features/reportable_note/merge_request_spec.rb +++ b/spec/features/reportable_note/merge_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Reportable note on merge request', :js do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } before do diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb index 9a14024684c..f1e48ed46be 100644 --- a/spec/features/reportable_note/snippets_spec.rb +++ b/spec/features/reportable_note/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Reportable note on snippets', :js do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.add_master(user) diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 1725b70acf3..cac31c34ad1 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -9,13 +9,13 @@ describe "Runners" do describe "specific runners" do before do - @project = FactoryGirl.create :empty_project, shared_runners_enabled: false + @project = FactoryGirl.create :project, shared_runners_enabled: false @project.team << [user, :master] - @project2 = FactoryGirl.create :empty_project + @project2 = FactoryGirl.create :project @project2.team << [user, :master] - @project3 = FactoryGirl.create :empty_project + @project3 = FactoryGirl.create :project @project3.team << [user, :developer] @shared_runner = FactoryGirl.create :ci_runner, :shared @@ -70,7 +70,7 @@ describe "Runners" do describe "shared runners" do before do - @project = FactoryGirl.create :empty_project, shared_runners_enabled: false + @project = FactoryGirl.create :project, shared_runners_enabled: false @project.team << [user, :master] visit runners_path(@project) end @@ -87,7 +87,7 @@ describe "Runners" do before do stub_application_setting(shared_runners_text: shared_runners_text) - project = FactoryGirl.create :empty_project, shared_runners_enabled: false + project = FactoryGirl.create :project, shared_runners_enabled: false project.team << [user, :master] visit runners_path(project) end @@ -99,7 +99,7 @@ describe "Runners" do describe "show page" do before do - @project = FactoryGirl.create :empty_project + @project = FactoryGirl.create :project @project.team << [user, :master] @specific_runner = FactoryGirl.create :ci_runner @project.runners << @specific_runner @@ -113,7 +113,7 @@ describe "Runners" do end feature 'configuring runners ability to picking untagged jobs' do - given(:project) { create(:empty_project) } + given(:project) { create(:project) } given(:runner) { create(:ci_runner) } background do diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 20dcb640c9c..52300b9d1dd 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -4,7 +4,7 @@ describe "Search" do include FilteredSearchHelpers let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, assignees: [user]) } let!(:issue2) { create(:issue, project: project, author: user) } @@ -20,7 +20,7 @@ describe "Search" do context 'search filters', js: true do let(:group) { create(:group) } - let!(:group_project) { create(:empty_project, group: group) } + let!(:group_project) { create(:project, group: group) } before do group.add_owner(user) diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index bf7be33013e..5067f0b0a49 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -4,7 +4,7 @@ describe 'Internal Group access' do include AccessMatchers let(:group) { create(:group, :internal) } - let(:project) { create(:empty_project, :internal, group: group) } + let(:project) { create(:project, :internal, group: group) } let(:project_guest) do create(:user) do |user| project.add_guest(user) diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index c399d7a0851..ff32413dc7e 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -4,7 +4,7 @@ describe 'Private Group access' do include AccessMatchers let(:group) { create(:group, :private) } - let(:project) { create(:empty_project, :private, group: group) } + let(:project) { create(:project, :private, group: group) } let(:project_guest) do create(:user) do |user| project.add_guest(user) diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 63e4d7ca65c..16d114fb3f7 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -4,7 +4,7 @@ describe 'Public Group access' do include AccessMatchers let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, :public, group: group) } + let(:project) { create(:project, :public, group: group) } let(:project_guest) do create(:user) do |user| project.add_guest(user) diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb index 178782af56c..d7dc99c0a57 100644 --- a/spec/features/security/project/snippet/internal_access_spec.rb +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe "Internal Project Snippets Access" do include AccessMatchers - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb index 7725c25ca1f..3ec1a388185 100644 --- a/spec/features/security/project/snippet/private_access_spec.rb +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe "Private Project Snippets Access" do include AccessMatchers - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb index 52aec75dcd0..39b104bfe27 100644 --- a/spec/features/security/project/snippet/public_access_spec.rb +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe "Public Project Snippets Access" do include AccessMatchers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) } let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb index ae3b876f87c..96c50f6c804 100644 --- a/spec/features/snippets_spec.rb +++ b/spec/features/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Snippets' do context 'when the project has snippets' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } before do allow(Snippet).to receive(:default_per_page).and_return(1) diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index ed7c5bd6592..56bb6845f65 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Master creates tag' do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } before do project.team << [user, :master] diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index e3c904ef3aa..4d6fc13557f 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Master deletes tag' do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } before do project.team << [user, :master] diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb index d6e84a1c685..b93ad44dfd3 100644 --- a/spec/features/tags/master_updates_tag_spec.rb +++ b/spec/features/tags/master_updates_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' feature 'Master updates tag' do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } before do project.team << [user, :master] diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb index 27936bc7f52..9edc7ced163 100644 --- a/spec/features/tags/master_views_tags_spec.rb +++ b/spec/features/tags/master_views_tags_spec.rb @@ -26,7 +26,7 @@ feature 'Master views tags' do end context 'when project has tags' do - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } let(:repository) { project.repository } before do diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 7e198ce0677..c14826df55a 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Task Lists' do include Warden::Test::Helpers - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 604fe326e96..8d12a492feb 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -9,7 +9,7 @@ feature 'Triggers', js: true do before do sign_in(user) - @project = create(:empty_project) + @project = create(:project) @project.team << [user, :master] @project.team << [user2, :master] @project.team << [guest_user, :guest] diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index d728dc59b3f..392d8e3e1c1 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -5,7 +5,7 @@ describe 'Unsubscribe links' do let(:recipient) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:params) { { title: 'A bug!', description: 'Fix it!', assignees: [recipient] } } let(:issue) { Issues::CreateService.new(project, author, params).execute } diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 0654923d9a6..53cad623a35 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -4,7 +4,7 @@ feature 'User uploads file to note' do include DropzoneHelper let(:user) { create(:user) } - let(:project) { create(:empty_project, creator: user, namespace: user.namespace) } + let(:project) { create(:project, creator: user, namespace: user.namespace) } let(:issue) { create(:issue, project: project, author: user) } before do diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb index fa35fd7ebab..95a3198a346 100644 --- a/spec/features/user_callout_spec.rb +++ b/spec/features/user_callout_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'User Callouts', js: true do let(:user) { create(:user) } let(:another_user) { create(:user) } - let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } + let(:project) { create(:project, path: 'gitlab', name: 'sample') } before do sign_in(user) diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb index b961d2337ed..f079771cee1 100644 --- a/spec/features/users/projects_spec.rb +++ b/spec/features/users/projects_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe 'Projects tab on a user profile', :js do let(:user) { create(:user) } - let!(:project) { create(:empty_project, namespace: user.namespace) } - let!(:project2) { create(:empty_project, namespace: user.namespace) } + let!(:project) { create(:project, namespace: user.namespace) } + let!(:project2) { create(:project, namespace: user.namespace) } before do allow(Project).to receive(:default_per_page).and_return(1) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index ff004d85272..1124b42721f 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -104,7 +104,7 @@ feature 'Users', js: true do end def errors_on_page(page) - page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n") + page.find('#error_explanation').find('ul').all('li').map { |item| item.text }.join("\n") end def number_of_errors_on_page(page) diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index dd770fe5043..6794bf4f4ba 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Project variables', js: true do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } before do diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb index 1d0c15392b2..0789d3a9b44 100644 --- a/spec/finders/access_requests_finder_spec.rb +++ b/spec/finders/access_requests_finder_spec.rb @@ -5,7 +5,7 @@ describe AccessRequestsFinder do let(:access_requester) { create(:user) } let(:project) do - create(:empty_project, :public, :access_requestable) do |project| + create(:project, :public, :access_requestable) do |project| project.request_access(access_requester) end end diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb index 296d6c51d04..4e367d39cf3 100644 --- a/spec/finders/admin/projects_finder_spec.rb +++ b/spec/finders/admin/projects_finder_spec.rb @@ -6,19 +6,19 @@ describe Admin::ProjectsFinder do let(:group) { create(:group, :public) } let!(:private_project) do - create(:empty_project, :private, name: 'A', path: 'A') + create(:project, :private, name: 'A', path: 'A') end let!(:internal_project) do - create(:empty_project, :internal, group: group, name: 'B', path: 'B') + create(:project, :internal, group: group, name: 'B', path: 'B') end let!(:public_project) do - create(:empty_project, :public, group: group, name: 'C', path: 'C') + create(:project, :public, group: group, name: 'C', path: 'C') end let!(:shared_project) do - create(:empty_project, :private, name: 'D', path: 'D') + create(:project, :private, name: 'D', path: 'D') end let(:params) { {} } @@ -40,7 +40,7 @@ describe Admin::ProjectsFinder do context 'filter by namespace_id' do let(:namespace) { create(:namespace) } - let!(:project_in_namespace) { create(:empty_project, namespace: namespace) } + let!(:project_in_namespace) { create(:project, namespace: namespace) } let(:params) { { namespace_id: namespace.id } } it { is_expected.to eq([project_in_namespace]) } @@ -99,7 +99,7 @@ describe Admin::ProjectsFinder do end context 'filter by archived' do - let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') } + let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') } context 'archived=false' do let(:params) { { archived: false } } @@ -115,7 +115,7 @@ describe Admin::ProjectsFinder do end context 'filter by personal' do - let!(:personal_project) { create(:empty_project, namespace: user.namespace) } + let!(:personal_project) { create(:project, namespace: user.namespace) } let(:params) { { personal: true } } it { is_expected.to eq([personal_project]) } diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb index 34f665826b6..2d079ea83b4 100644 --- a/spec/finders/contributed_projects_finder_spec.rb +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -6,8 +6,8 @@ describe ContributedProjectsFinder do let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:empty_project, :public) } - let!(:private_project) { create(:empty_project, :private) } + let!(:public_project) { create(:project, :public) } + let!(:private_project) { create(:project, :private) } before do private_project.add_master(source_user) diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb index 30a2bd14f10..18d6c0cfd74 100644 --- a/spec/finders/events_finder_spec.rb +++ b/spec/finders/events_finder_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe EventsFinder do let(:user) { create(:user) } let(:other_user) { create(:user) } - let(:project1) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) } + let(:project1) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } let(:closed_issue) { create(:closed_issue, project: project1, author: user) } let(:opened_merge_request) { create(:merge_request, source_project: project2, author: user) } let!(:closed_issue_event) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) } diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index 3c7c9bdcd08..c6d257bc479 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -7,11 +7,11 @@ describe GroupProjectsFinder do let(:finder) { described_class.new(group: group, current_user: current_user, options: options) } - let!(:public_project) { create(:empty_project, :public, group: group, path: '1') } - let!(:private_project) { create(:empty_project, :private, group: group, path: '2') } - let!(:shared_project_1) { create(:empty_project, :public, path: '3') } - let!(:shared_project_2) { create(:empty_project, :private, path: '4') } - let!(:shared_project_3) { create(:empty_project, :internal, path: '5') } + let!(:public_project) { create(:project, :public, group: group, path: '1') } + let!(:private_project) { create(:project, :private, group: group, path: '2') } + let!(:shared_project_1) { create(:project, :public, path: '3') } + let!(:shared_project_2) { create(:project, :private, path: '4') } + let!(:shared_project_3) { create(:project, :internal, path: '5') } before do shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index 9e70cccc3c4..abc470788e1 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -80,7 +80,7 @@ describe GroupsFinder do context 'authorized to private project' do context 'project one level deep' do - let!(:subproject) { create(:empty_project, :private, namespace: private_subgroup) } + let!(:subproject) { create(:project, :private, namespace: private_subgroup) } before do subproject.add_guest(user) end @@ -98,7 +98,7 @@ describe GroupsFinder do context 'project two levels deep' do let!(:private_subsubgroup) { create(:group, :private, parent: private_subgroup) } - let!(:subsubproject) { create(:empty_project, :private, namespace: private_subsubgroup) } + let!(:subsubproject) { create(:project, :private, namespace: private_subsubgroup) } before do subsubproject.add_guest(user) end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index bef4fd44331..8769a52863c 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe IssuesFinder do set(:user) { create(:user) } set(:user2) { create(:user) } - set(:project1) { create(:empty_project) } - set(:project2) { create(:empty_project) } + set(:project1) { create(:project) } + set(:project2) { create(:project) } set(:milestone) { create(:milestone, project: project1) } set(:label) { create(:label, project: project2) } set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) } @@ -87,9 +87,9 @@ describe IssuesFinder do context 'filtering by upcoming milestone' do let(:params) { { milestone_title: Milestone::Upcoming.name } } - let(:project_no_upcoming_milestones) { create(:empty_project, :public) } - let(:project_next_1_1) { create(:empty_project, :public) } - let(:project_next_8_8) { create(:empty_project, :public) } + let(:project_no_upcoming_milestones) { create(:project, :public) } + let(:project_next_1_1) { create(:project, :public) } + let(:project_next_8_8) { create(:project, :public) } let(:yesterday) { Date.today - 1.day } let(:tomorrow) { Date.today + 1.day } @@ -121,9 +121,9 @@ describe IssuesFinder do context 'filtering by started milestone' do let(:params) { { milestone_title: Milestone::Started.name } } - let(:project_no_started_milestones) { create(:empty_project, :public) } - let(:project_started_1_and_2) { create(:empty_project, :public) } - let(:project_started_8) { create(:empty_project, :public) } + let(:project_no_started_milestones) { create(:project, :public) } + let(:project_started_1_and_2) { create(:project, :public) } + let(:project_started_8) { create(:project, :public) } let(:yesterday) { Date.today - 1.day } let(:tomorrow) { Date.today + 1.day } @@ -268,7 +268,7 @@ describe IssuesFinder do it 'finds issues user can access due to group' do group = create(:group) - project = create(:empty_project, group: group) + project = create(:project, group: group) issue = create(:issue, project: project) group.add_user(user, :owner) @@ -296,7 +296,7 @@ describe IssuesFinder do let(:scope) { nil } it "doesn't return team-only issues to non team members" do - project = create(:empty_project, :public, :issues_private) + project = create(:project, :public, :issues_private) issue = create(:issue, project: project) expect(issues).not_to include(issue) @@ -315,7 +315,7 @@ describe IssuesFinder do describe '#with_confidentiality_access_check' do let(:guest) { create(:user) } set(:authorized_user) { create(:user) } - set(:project) { create(:empty_project, namespace: authorized_user.namespace) } + set(:project) { create(:project, namespace: authorized_user.namespace) } set(:public_issue) { create(:issue, project: project) } set(:confidential_issue) { create(:issue, project: project, confidential: true) } diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb index 4c389746252..29a47e005a6 100644 --- a/spec/finders/joined_groups_finder_spec.rb +++ b/spec/finders/joined_groups_finder_spec.rb @@ -42,7 +42,7 @@ describe JoinedGroupsFinder do context 'if profile visitor is in one of the private group projects' do before do - project = create(:empty_project, :private, group: private_group, name: 'B', path: 'B') + project = create(:project, :private, group: private_group, name: 'B', path: 'B') project.add_user(profile_visitor, Gitlab::Access::DEVELOPER) end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 95d96354b77..afa2a40ed2a 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -6,11 +6,11 @@ describe LabelsFinder do let(:group_2) { create(:group) } let(:group_3) { create(:group) } - let(:project_1) { create(:empty_project, namespace: group_1) } - let(:project_2) { create(:empty_project, namespace: group_2) } - let(:project_3) { create(:empty_project) } - let(:project_4) { create(:empty_project, :public) } - let(:project_5) { create(:empty_project, namespace: group_1) } + let(:project_1) { create(:project, namespace: group_1) } + let(:project_2) { create(:project, namespace: group_2) } + let(:project_3) { create(:project) } + let(:project_4) { create(:project, :public) } + let(:project_5) { create(:project, namespace: group_1) } let!(:project_label_1) { create(:label, project: project_1, title: 'Label 1') } let!(:project_label_2) { create(:label, project: project_2, title: 'Label 2') } @@ -68,7 +68,7 @@ describe LabelsFinder do context 'as an administrator' do it 'does not return labels from another project' do # Purposefully creating a project with _nothing_ associated to it - isolated_project = create(:empty_project) + isolated_project = create(:project) admin = create(:admin) # project_3 has a label associated to it, which we don't want coming diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index b46218bf72e..b54155a6704 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -4,9 +4,9 @@ describe MergeRequestsFinder do let(:user) { create :user } let(:user2) { create :user } - let(:project1) { create(:empty_project) } - let(:project2) { create(:empty_project, forked_from_project: project1) } - let(:project3) { create(:empty_project, :archived, forked_from_project: project1) } + let(:project1) { create(:project) } + let(:project2) { create(:project, forked_from_project: project1) } + let(:project3) { create(:project, :archived, forked_from_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } @@ -67,7 +67,7 @@ describe MergeRequestsFinder do end context 'with created_after and created_before params' do - let(:project4) { create(:empty_project, forked_from_project: project1) } + let(:project4) { create(:project, forked_from_project: project1) } let!(:new_merge_request) do create(:merge_request, diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb index 32ec983c5b8..8ae08656e01 100644 --- a/spec/finders/milestones_finder_spec.rb +++ b/spec/finders/milestones_finder_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe MilestonesFinder do let(:group) { create(:group) } - let(:project_1) { create(:empty_project, namespace: group) } - let(:project_2) { create(:empty_project, namespace: group) } + let(:project_1) { create(:project, namespace: group) } + let(:project_2) { create(:project, namespace: group) } let!(:milestone_1) { create(:milestone, group: group, title: 'one test', due_date: Date.today) } let!(:milestone_2) { create(:milestone, group: group) } let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) } diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index dea87980e25..e577083a2d0 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe MoveToProjectFinder do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } - let(:no_access_project) { create(:empty_project) } - let(:guest_project) { create(:empty_project) } - let(:reporter_project) { create(:empty_project) } - let(:developer_project) { create(:empty_project) } - let(:master_project) { create(:empty_project) } + let(:no_access_project) { create(:project) } + let(:guest_project) { create(:project) } + let(:reporter_project) { create(:project) } + let(:developer_project) { create(:project) } + let(:master_project) { create(:project) } subject { described_class.new(user) } @@ -37,7 +37,7 @@ describe MoveToProjectFinder do it 'does not return archived projects' do reporter_project.team << [user, :reporter] reporter_project.archive! - other_reporter_project = create(:empty_project) + other_reporter_project = create(:project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -46,7 +46,7 @@ describe MoveToProjectFinder do it 'does not return projects for which issues are disabled' do reporter_project.team << [user, :reporter] reporter_project.update_attributes(issues_enabled: false) - other_reporter_project = create(:empty_project) + other_reporter_project = create(:project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -83,10 +83,10 @@ describe MoveToProjectFinder do end it 'returns projects matching a search query' do - foo_project = create(:empty_project) + foo_project = create(:project) foo_project.team << [user, :master] - wadus_project = create(:empty_project, name: 'wadus') + wadus_project = create(:project, name: 'wadus') wadus_project.team << [user, :master] expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index ba6bbb3bce0..900fa2b12d1 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe NotesFinder do let(:user) { create :user } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.team << [user, :master] @@ -43,7 +43,7 @@ describe NotesFinder do context 'on restricted projects' do let(:project) do - create(:empty_project, + create(:project, :public, :issues_private, :snippets_private, @@ -156,7 +156,7 @@ describe NotesFinder do end describe '.search' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:note) { create(:note_on_issue, note: 'WoW', project: project) } it 'returns notes with matching content' do diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index 304b0fb67fb..d0113ba87df 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -4,14 +4,14 @@ describe PersonalProjectsFinder do let(:source_user) { create(:user) } let(:current_user) { create(:user) } let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:empty_project, :public, namespace: source_user.namespace) } + let!(:public_project) { create(:project, :public, namespace: source_user.namespace) } let!(:private_project) do - create(:empty_project, :private, namespace: source_user.namespace, path: 'mepmep') + create(:project, :private, namespace: source_user.namespace, path: 'mepmep') end let!(:internal_project) do - create(:empty_project, :internal, namespace: source_user.namespace, path: 'C') + create(:project, :internal, namespace: source_user.namespace, path: 'C') end before do diff --git a/spec/finders/pipeline_schedules_finder_spec.rb b/spec/finders/pipeline_schedules_finder_spec.rb index e184a87c9c7..b9538649b3f 100644 --- a/spec/finders/pipeline_schedules_finder_spec.rb +++ b/spec/finders/pipeline_schedules_finder_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe PipelineSchedulesFinder do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) } let!(:inactive_schedule) { create(:ci_pipeline_schedule, :inactive, project: project) } diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 03d98459e8c..a5de586e869 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -6,19 +6,19 @@ describe ProjectsFinder do let(:group) { create(:group, :public) } let!(:private_project) do - create(:empty_project, :private, name: 'A', path: 'A') + create(:project, :private, name: 'A', path: 'A') end let!(:internal_project) do - create(:empty_project, :internal, group: group, name: 'B', path: 'B') + create(:project, :internal, group: group, name: 'B', path: 'B') end let!(:public_project) do - create(:empty_project, :public, group: group, name: 'C', path: 'C') + create(:project, :public, group: group, name: 'C', path: 'C') end let!(:shared_project) do - create(:empty_project, :private, name: 'D', path: 'D') + create(:project, :private, name: 'D', path: 'D') end let(:params) { {} } @@ -90,7 +90,7 @@ describe ProjectsFinder do end describe 'filter by personal' do - let!(:personal_project) { create(:empty_project, namespace: user.namespace) } + let!(:personal_project) { create(:project, namespace: user.namespace) } let(:params) { { personal: true } } it { is_expected.to eq([personal_project]) } @@ -109,7 +109,7 @@ describe ProjectsFinder do end describe 'filter by archived' do - let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') } + let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') } context 'non_archived=true' do let(:params) { { non_archived: true } } @@ -139,7 +139,7 @@ describe ProjectsFinder do describe 'filter by owned' do let(:params) { { owned: true } } - let!(:owned_project) { create(:empty_project, :private, namespace: current_user.namespace) } + let!(:owned_project) { create(:project, :private, namespace: current_user.namespace) } it { is_expected.to eq([owned_project]) } end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 35f1683eef9..7ae7b7d2140 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -5,8 +5,8 @@ describe SnippetsFinder do let(:user1) { create :user } let(:group) { create :group, :public } - let(:project1) { create(:empty_project, :public, group: group) } - let(:project2) { create(:empty_project, :private, group: group) } + let(:project1) { create(:project, :public, group: group) } + let(:project2) { create(:project, :private, group: group) } context 'all snippets visible to a user' do let!(:snippet1) { create(:personal_snippet, :private) } diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 8be447418b0..884ce22091e 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe TodosFinder do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:finder) { described_class } before do diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json index 7ffa82fc4bd..2f12b671dec 100644 --- a/spec/fixtures/api/schemas/entities/merge_request.json +++ b/spec/fixtures/api/schemas/entities/merge_request.json @@ -19,7 +19,6 @@ "human_time_estimate": { "type": ["integer", "null"] }, "human_total_time_spent": { "type": ["integer", "null"] }, "in_progress_merge_commit_sha": { "type": ["string", "null"] }, - "locked_at": { "type": ["string", "null"] }, "merge_error": { "type": ["string", "null"] }, "merge_commit_sha": { "type": ["string", "null"] }, "merge_params": { "type": ["object", "null"] }, @@ -94,7 +93,8 @@ "commit_change_content_path": { "type": "string" }, "remove_wip_path": { "type": "string" }, "commits_count": { "type": "integer" }, - "remove_source_branch": { "type": ["boolean", "null"] } + "remove_source_branch": { "type": ["boolean", "null"] }, + "merge_ongoing": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v4/comment.json b/spec/fixtures/api/schemas/public_api/v4/comment.json new file mode 100644 index 00000000000..52cfe86aeeb --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/comment.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required" : [ + "name", + "message", + "commit", + "release" + ], + "properties" : { + "name": { "type": "string" }, + "message": { "type": ["string", "null"] }, + "commit": { "$ref": "commit/basic.json" }, + "release": { + "oneOf": [ + { "type": "null" }, + { "$ref": "release.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json new file mode 100644 index 00000000000..b7b2535c204 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "allOf": [ + { "$ref": "basic.json" }, + { + "required" : [ + "stats", + "status" + ], + "properties": { + "stats": { "$ref": "../commit_stats.json" }, + "status": { "type": ["string", "null"] } + } + } + ] +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_note.json b/spec/fixtures/api/schemas/public_api/v4/commit_note.json new file mode 100644 index 00000000000..02081989271 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commit_note.json @@ -0,0 +1,19 @@ +{ + "type": "object", + "required" : [ + "note", + "path", + "line", + "line_type", + "author", + "created_at" + ], + "properties" : { + "note": { "type": ["string", "null"] }, + "path": { "type": ["string", "null"] }, + "line": { "type": ["integer", "null"] }, + "line_type": { "type": ["string", "null"] }, + "author": { "$ref": "user/basic.json" }, + "created_at": { "type": "date" } + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_notes.json b/spec/fixtures/api/schemas/public_api/v4/commit_notes.json new file mode 100644 index 00000000000..d65a7d677ea --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commit_notes.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "commit_note.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_stats.json b/spec/fixtures/api/schemas/public_api/v4/commit_stats.json new file mode 100644 index 00000000000..779384c62e6 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commit_stats.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "required" : [ + "additions", + "deletions", + "total" + ], + "properties" : { + "additions": { "type": "integer" }, + "deletions": { "type": "integer" }, + "total": { "type": "integer" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commits.json b/spec/fixtures/api/schemas/public_api/v4/commits.json new file mode 100644 index 00000000000..98b17a96071 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commits.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "commit/basic.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json new file mode 100644 index 00000000000..6612c2a9911 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required" : [ + "tag_name", + "description" + ], + "properties" : { + "tag_name": { "type": ["string", "null"] }, + "description": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json new file mode 100644 index 00000000000..52cfe86aeeb --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/tag.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required" : [ + "name", + "message", + "commit", + "release" + ], + "properties" : { + "name": { "type": "string" }, + "message": { "type": ["string", "null"] }, + "commit": { "$ref": "commit/basic.json" }, + "release": { + "oneOf": [ + { "type": "null" }, + { "$ref": "release.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/tags.json b/spec/fixtures/api/schemas/public_api/v4/tags.json new file mode 100644 index 00000000000..eae352e7f87 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/tags.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "tag.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json new file mode 100644 index 00000000000..9f69d31971c --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "required": [ + "id", + "state", + "avatar_url", + "web_url" + ], + "properties": { + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "string" }, + "web_url": { "type": "string" } + } +} diff --git a/spec/fixtures/encoding/Japanese.md b/spec/fixtures/encoding/Japanese.md new file mode 100644 index 00000000000..dd469c9f232 --- /dev/null +++ b/spec/fixtures/encoding/Japanese.md @@ -0,0 +1,42 @@ ++++ +date = "2017-05-21T13:05:07+09:00" +title = "レイヤ" +weight = 10 + ++++ + +## このチュートリアルで扱う内容 +1. Redactedにおける2D開発でのレイヤの基本的な概要 +2. スクリーン上のスプライトの順序付け方法 + +### Redactedにおける2D開発でのレイヤの基本的な概要 +2Dにおいてはz軸が存在しないため、シーン内要素の描画順を制御するためには代替となる仕組みが必要です。 +Redactedでは**レイヤ**における**zIndex**属性を制御可能にすることで、この課題を解決しています。 +**デフォルトでは、zIndexは0となりオブジェクトはレイヤに追加された順番に描画されます。** + +レイヤにはいくつかの重要な特性があります。 + +* レイヤにはレイヤ化されたオブジェクトのみを含めることができます。(**3Dモデルは絶対に追加しないでください**) +* レイヤはレイヤ化されたオブジェクトです。(したがって、レイヤには他のレイヤを含めることができます) +* レイヤ化されたオブジェクトは、最大で1つのレイヤに属すことができます。 + +レイヤを直接初期化することはできませんが、その派生クラスは初期化することが可能です。**Scene2D**と**コンテナ**は、**レイヤ**から派生する2つの主なオブジェクトです。すべての初期化(createContainer、instantiate、...)はレイヤ上で行われます。つまり、2Dで初期化されるすべてのオブジェクトは、zIndexプロパティを持つレイヤ化されたオブジェクトです。 + +**zIndexはグローバルではありません!** + +CSSとは異なり、zIndexはすべてのオブジェクトに対してグローバルではありません。zIndexプロパティは親レイヤに対してローカルです。詳細につきましては、コンテナチュートリアルで説明しています。 [TODO: Link]。 + +### スクリーン上のスプライトの順序付け方法 +これまで学んだことを生かして、画面にスプライトを表示して、zIndexの設定をしてみましょう! + +* まず、最初に (A,B,C) スプライトを生成します。 +* スプライトAをシーンに追加します(zIndex = 0、標準色) +* スプライトBをシーン2に追加すると、**スプライトAの上に**表示されます(zIndex = 0、赤色) +* 最後にスプライトCをシーンに追加します(青色)が、スプライトのzIndexを-1に設定すると、スプライトはAとBの後側に表示されます。 + +{{< code "static/tutorials/layers.html" >}} + +### ソースコード全体 +```js +{{< snippet "static/tutorials/layers.html" >}} +``` diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 58b43805705..4f46e40ce7a 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -227,8 +227,11 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Milestone in another project: <%= xmilestone.to_reference(project) %> - Ignored in code: `<%= simple_milestone.to_reference %>` - Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link) -- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %> +- Milestone by URL: <%= urls.milestone_url(milestone) %> - Link to milestone by URL: [Milestone](<%= milestone.to_reference %>) +- Group milestone by name: <%= Milestone.reference_prefix %><%= group_milestone.name %> +- Group milestone by name in quotes: <%= group_milestone.to_reference(format: :name) %> +- Group milestone by URL is ignore: <%= urls.milestone_url(group_milestone) %> ### Task Lists diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index ac5a58ac189..10bc5f2ecd2 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -58,7 +58,7 @@ describe ApplicationHelper do describe 'project_icon' do it 'returns an url for the avatar' do - project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) + project = create(:project, avatar: File.open(uploaded_image_temp_path)) avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) @@ -72,7 +72,7 @@ describe ApplicationHelper do end it 'gives uploaded icon when present' do - project = create(:empty_project) + project = create(:project) allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index bd3a3d24b84..c654151564e 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -108,7 +108,7 @@ describe BlobHelper do context 'viewer related' do include FakeBlobHelpers - let(:project) { build(:empty_project, lfs_enabled: true) } + let(:project) { build(:project, lfs_enabled: true) } before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 7ecb75da8ce..250ba239033 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ButtonHelper do describe 'http_clone_button' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { build_stubbed(:project) } let(:has_tooltip_class) { 'has-tooltip' } def element diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index e6bb953e9d8..6a3945c0ebc 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -48,7 +48,7 @@ describe CiStatusHelper do describe "#pipeline_status_cache_key" do it "builds a cache key for pipeline status" do pipeline_status = Gitlab::Cache::Ci::ProjectPipelineStatus.new( - build(:project), + build_stubbed(:project), pipeline_info: { sha: "123abc", status: "success" diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index c245bb439db..7179185285c 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -28,7 +28,7 @@ describe CommitsHelper do end describe '#view_on_environment_button' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:environment) { create(:environment, external_url: 'http://example.com') } let(:path) { 'source/file.html' } let(:sha) { RepoHelpers.sample_commit.id } diff --git a/spec/helpers/defer_script_tag_helper_spec.rb b/spec/helpers/defer_script_tag_helper_spec.rb new file mode 100644 index 00000000000..d10b6f134e4 --- /dev/null +++ b/spec/helpers/defer_script_tag_helper_spec.rb @@ -0,0 +1,13 @@ +# coding: utf-8 +require 'spec_helper' + +describe DeferScriptTagHelper do + describe 'script tag' do + script_url = 'test.js' + + it 'returns an script tag with defer=true' do + expect(javascript_include_tag(script_url).to_s) + .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\"></script>" + end + end +end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 060c112e20d..f81a9b6492c 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe DiffHelper do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index c3bd0cb3542..aa138f25bd3 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -72,13 +72,13 @@ describe EventsHelper do end it 'preserves style attribute for a label that can be accessed by current_user' do - project = create(:empty_project, :public) + project = create(:project, :public) expect(format_event_note(project)).to match(/span class=.*style=.*/) end it 'does not style a label that can not be accessed by current_user' do - project = create(:empty_project, :private) + project = create(:project, :private) expect(format_event_note(project)).to eq("<p>#{input}</p>") end diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 9aaed0edf87..a44b200c5da 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe GitlabRoutingHelper do - let(:project) { build_stubbed(:empty_project) } + let(:project) { build_stubbed(:project) } let(:group) { build_stubbed(:group) } describe 'Project URL helpers' do @@ -63,44 +63,4 @@ describe GitlabRoutingHelper do it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) } end end - - describe '#milestone_path' do - context 'for a group milestone' do - let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } - - it 'links to the group milestone page' do - expect(milestone_path(milestone)) - .to eq(group_milestone_path(group, milestone)) - end - end - - context 'for a project milestone' do - let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } - - it 'links to the project milestone page' do - expect(milestone_path(milestone)) - .to eq(project_milestone_path(project, milestone)) - end - end - end - - describe '#milestone_url' do - context 'for a group milestone' do - let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } - - it 'links to the group milestone page' do - expect(milestone_url(milestone)) - .to eq(group_milestone_url(group, milestone)) - end - end - - context 'for a project milestone' do - let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } - - it 'links to the project milestone page' do - expect(milestone_url(milestone)) - .to eq(project_milestone_url(project, milestone)) - end - end - end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 3a246f10283..9d6e03e3868 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -23,7 +23,7 @@ describe GroupsHelper do describe 'group_lfs_status' do let(:group) { create(:group) } - let!(:project) { create(:empty_project, namespace_id: group.id) } + let!(:project) { create(:project, namespace_id: group.id) } before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) @@ -47,7 +47,7 @@ describe GroupsHelper do context 'more than one project in group' do before do - create(:empty_project, namespace_id: group.id) + create(:project, namespace_id: group.id) end context 'LFS enabled in group' do diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb index 9f0004bf8cf..2e21f1134b1 100644 --- a/spec/helpers/hooks_helper_spec.rb +++ b/spec/helpers/hooks_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe HooksHelper do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:project_hook) { create(:project_hook, project: project) } let(:system_hook) { create(:system_hook) } let(:trigger) { 'push_events' } diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 9524a101e74..dc3100311f8 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe IssuesHelper do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create :issue, project: project } let(:ext_project) { create :redmine_project } diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index a8d6044fda7..36d6e495ed0 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -2,18 +2,18 @@ require 'spec_helper' describe LabelsHelper do describe 'link_to_label' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:label) { create(:label, project: project) } context 'without subject' do it "uses the label's project" do - expect(link_to_label(label)).to match %r{<a href="/#{label.project.path_with_namespace}/issues\?label_name%5B%5D=#{label.name}">.*</a>} + expect(link_to_label(label)).to match %r{<a href="/#{label.project.full_path}/issues\?label_name%5B%5D=#{label.name}">.*</a>} end end context 'with a project as subject' do let(:namespace) { build(:namespace, name: 'foo3') } - let(:another_project) { build(:empty_project, namespace: namespace, name: 'bar3') } + let(:another_project) { build(:project, namespace: namespace, name: 'bar3') } it 'links to project issues page' do expect(link_to_label(label, subject: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name%5B%5D=#{label.name}">.*</a>} @@ -32,7 +32,7 @@ describe LabelsHelper do ['issue', :issue, 'merge_request', :merge_request].each do |type| context "set to #{type}" do it 'links to correct page' do - expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.path_with_namespace}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>} + expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.full_path}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>} end end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 4b6a351cf70..70eb01c9c44 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -210,11 +210,11 @@ describe MarkupHelper do describe '#cross_project_reference' do it 'shows the full MR reference' do - expect(helper.cross_project_reference(project, merge_request)).to include(project.path_with_namespace) + expect(helper.cross_project_reference(project, merge_request)).to include(project.full_path) end it 'shows the full issue reference' do - expect(helper.cross_project_reference(project, issue)).to include(project.path_with_namespace) + expect(helper.cross_project_reference(project, issue)).to include(project.full_path) end end end diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 2b455571d52..33186cf50d5 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -11,7 +11,7 @@ describe MembersHelper do describe '#remove_member_message' do let(:requester) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:project_member) { build(:project_member, project: project) } let(:project_member_invite) { build(:project_member, project: project).tap { |m| m.generate_invite_token! } } let(:project_member_request) { project.request_access(requester) } @@ -32,7 +32,7 @@ describe MembersHelper do describe '#remove_member_title' do let(:requester) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:project_member) { build(:project_member, project: project) } let(:project_member_request) { project.request_access(requester) } let(:group) { create(:group, :access_requestable) } @@ -46,7 +46,7 @@ describe MembersHelper do end describe '#leave_confirmation_message' do - let(:project) { build_stubbed(:empty_project) } + let(:project) { build_stubbed(:project) } let(:group) { build_stubbed(:group) } let(:user) { build_stubbed(:user) } diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 493a4ff9a93..7d1c17909bf 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequestsHelper do describe 'ci_build_details_path' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:merge_request) { MergeRequest.new } let(:ci_service) { CiService.new } let(:last_commit) { Ci::Pipeline.new({}) } @@ -30,12 +30,12 @@ describe MergeRequestsHelper do end describe 'within different projects' do - let(:project) { create(:empty_project) } - let(:fork_project) { create(:empty_project, forked_from_project: project) } + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } subject { format_mr_branch_names(merge_request) } - let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" } - let(:target_title) { "#{project.path_with_namespace}:#{merge_request.target_branch}" } + let(:source_title) { "#{fork_project.full_path}:#{merge_request.source_branch}" } + let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" } it { is_expected.to eq([source_title, target_title]) } end diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb index b8f9c02a486..70b4a89cb86 100644 --- a/spec/helpers/milestones_helper_spec.rb +++ b/spec/helpers/milestones_helper_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe MilestonesHelper do describe '#milestones_filter_dropdown_path' do - let(:project) { create(:empty_project) } - let(:project2) { create(:empty_project) } + let(:project) { create(:project) } + let(:project2) { create(:project) } let(:group) { create(:group) } context 'when @project present' do @@ -57,7 +57,7 @@ describe MilestonesHelper do end describe '#milestone_counts' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:counts) { helper.milestone_counts(project.milestones) } context 'when there are milestones' do diff --git a/spec/helpers/milestones_routing_helper_spec.rb b/spec/helpers/milestones_routing_helper_spec.rb new file mode 100644 index 00000000000..dc13a43c2ab --- /dev/null +++ b/spec/helpers/milestones_routing_helper_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe MilestonesRoutingHelper do + let(:project) { build_stubbed(:project) } + let(:group) { build_stubbed(:group) } + + describe '#milestone_path' do + context 'for a group milestone' do + let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } + + it 'links to the group milestone page' do + expect(milestone_path(milestone)) + .to eq(group_milestone_path(group, milestone)) + end + end + + context 'for a project milestone' do + let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } + + it 'links to the project milestone page' do + expect(milestone_path(milestone)) + .to eq(project_milestone_path(project, milestone)) + end + end + end + + describe '#milestone_url' do + context 'for a group milestone' do + let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } + + it 'links to the group milestone page' do + expect(milestone_url(milestone)) + .to eq(group_milestone_url(group, milestone)) + end + end + + context 'for a project milestone' do + let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } + + it 'links to the project milestone page' do + expect(milestone_url(milestone)) + .to eq(project_milestone_url(project, milestone)) + end + end + end +end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 56f252ba273..9921ca1af33 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -5,7 +5,7 @@ describe NotesHelper do let(:owner) { create(:owner) } let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let(:master) { create(:user) } let(:reporter) { create(:user) } let(:guest) { create(:user) } @@ -30,7 +30,7 @@ describe NotesHelper do end it 'handles access in different projects' do - second_project = create(:empty_project) + second_project = create(:project) second_project.team << [master, :reporter] other_note = create(:note, author: master, project: second_project) @@ -40,7 +40,7 @@ describe NotesHelper do end describe '#discussion_path' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'for a merge request discusion' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) } @@ -191,7 +191,7 @@ describe NotesHelper do it 'return project notes path for project snippet' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) @snippet = create(:project_snippet, project: @project) @noteable = @snippet @@ -200,7 +200,7 @@ describe NotesHelper do it 'return project notes path for other noteables' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) @noteable = create(:issue, project: @project) expect(helper.notes_url).to eq("/nm/test/noteable/issue/#{@noteable.id}/notes") @@ -216,7 +216,7 @@ describe NotesHelper do it 'return project notes path for project snippet' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) note = create(:note_on_project_snippet, project: @project) expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}") @@ -224,7 +224,7 @@ describe NotesHelper do it 'return project notes path for other noteables' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) note = create(:note_on_issue, project: @project) expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}") @@ -241,7 +241,7 @@ describe NotesHelper do it 'returns namespace, project and note for project snippet' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) @snippet = create(:project_snippet, project: @project) @note = create(:note_on_personal_snippet) @@ -250,7 +250,7 @@ describe NotesHelper do it 'returns namespace, project and note path for other noteables' do namespace = create(:namespace, path: 'nm') - @project = create(:empty_project, path: 'test', namespace: namespace) + @project = create(:project, path: 'test', namespace: namespace) @note = create(:note_on_issue, project: @project) expect(helper.form_resources).to eq([@project.namespace, @project, @note]) @@ -258,7 +258,7 @@ describe NotesHelper do end describe '#noteable_note_url' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:note) { create(:note_on_issue, noteable: issue, project: project) } diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 45066a60f50..37a5e6b474e 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -46,25 +46,25 @@ describe ProjectsHelper do end describe "readme_cache_key" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do helper.instance_variable_set(:@project, project) end it "returns a valid cach key" do - expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-#{project.commit.id}-readme") + expect(helper.send(:readme_cache_key)).to eq("#{project.full_path}-#{project.commit.id}-readme") end it "returns a valid cache key if HEAD does not exist" do allow(project).to receive(:commit) { nil } - expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme") + expect(helper.send(:readme_cache_key)).to eq("#{project.full_path}-nil-readme") end end describe "#project_list_cache_key", clean_gitlab_redis_shared_state: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it "includes the route" do expect(helper.project_list_cache_key(project)).to include(project.route.cache_key) @@ -105,7 +105,7 @@ describe ProjectsHelper do describe '#load_pipeline_status' do it 'loads the pipeline status in batch' do - project = build(:empty_project) + project = build(:project) helper.load_pipeline_status([project]) # Skip lazy loading of the `pipeline_status` attribute @@ -193,7 +193,7 @@ describe ProjectsHelper do describe 'link_to_member' do let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } let(:user) { create(:user) } describe 'using the default options' do @@ -225,7 +225,7 @@ describe ProjectsHelper do end describe '#license_short_name' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when project.repository has a license_key' do it 'returns the nickname of the license if present' do @@ -251,7 +251,7 @@ describe ProjectsHelper do end describe '#sanitized_import_error' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do allow(project).to receive(:repository_storage_path).and_return('/base/repo/path') @@ -312,7 +312,7 @@ describe ProjectsHelper do end describe "#project_feature_access_select" do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } context "when project is internal or public" do @@ -380,7 +380,7 @@ describe ProjectsHelper do end describe '#get_project_nav_tabs' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } before do @@ -411,4 +411,48 @@ describe ProjectsHelper do end end end + + describe '#has_projects_or_name?' do + let(:projects) do + create(:project) + Project.all + end + + it 'returns true when there are projects' do + expect(helper.has_projects_or_name?(projects, {})).to eq(true) + end + + it 'returns true when there are no projects but a name is given' do + expect(helper.has_projects_or_name?(Project.none, name: 'foo')).to eq(true) + end + + it 'returns false when there are no projects and there is no name' do + expect(helper.has_projects_or_name?(Project.none, {})).to eq(false) + end + end + + describe '#any_projects?' do + before do + create(:project) + end + + it 'returns true when projects will be returned' do + expect(helper.any_projects?(Project.all)).to eq(true) + end + + it 'returns false when no projects will be returned' do + expect(helper.any_projects?(Project.none)).to eq(false) + end + + it 'only executes a single query when a LIMIT is applied' do + relation = Project.limit(1) + recorder = ActiveRecord::QueryRecorder.new do + 2.times do + helper.any_projects?(relation) + end + end + + expect(recorder.count).to eq(1) + end + end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index b7e547dc1f5..463af15930d 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -47,7 +47,7 @@ describe SearchHelper do end it "includes the user's projects" do - project = create(:empty_project, namespace: create(:namespace, owner: user)) + project = create(:project, namespace: create(:namespace, owner: user)) expect(search_autocomplete_opts(project.name).size).to eq(1) end @@ -68,4 +68,38 @@ describe SearchHelper do end end end + + describe 'search_filter_input_options' do + context 'project' do + before do + @project = create(:project, :repository) + end + + it 'includes id with type' do + expect(search_filter_input_options('type')[:id]).to eq('filtered-search-type') + end + + it 'includes project-id' do + expect(search_filter_input_options('')[:data]['project-id']).to eq(@project.id) + end + + it 'includes project base-endpoint' do + expect(search_filter_input_options('')[:data]['base-endpoint']).to eq(project_path(@project)) + end + end + + context 'group' do + before do + @group = create(:group, name: 'group') + end + + it 'does not includes project-id' do + expect(search_filter_input_options('')[:data]['project-id']).to eq(nil) + end + + it 'includes group base-endpoint' do + expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/groups#{group_path(@group)}") + end + end + end end diff --git a/spec/helpers/storage_health_helper_spec.rb b/spec/helpers/storage_health_helper_spec.rb new file mode 100644 index 00000000000..874498e6338 --- /dev/null +++ b/spec/helpers/storage_health_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe StorageHealthHelper do + describe '#failing_storage_health_message' do + let(:health) do + Gitlab::Git::Storage::Health.new( + "<script>alert('storage name');)</script>", + [] + ) + end + + it 'escapes storage names' do + escaped_storage_name = '<script>alert('storage name');)</script>' + + result = helper.failing_storage_health_message(health) + + expect(result).to include(escaped_storage_name) + end + end +end diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 9e561d0f191..c4f4e0d21dc 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -91,7 +91,7 @@ describe SubmoduleHelper do context 'in-repository submodule' do let(:group) { create(:group, name: "Master Project", path: "master-project") } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } before do self.instance_variable_set(:@project, project) end @@ -158,7 +158,7 @@ describe SubmoduleHelper do context 'submodules with relative links' do let(:group) { create(:group, name: "Master Project", path: "master-project") } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } let(:commit_id) { sample_commit[:id] } before do @@ -192,7 +192,7 @@ describe SubmoduleHelper do context 'personal project' do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } it 'one level down with personal project' do result = relative_self_links('../test.git', commit_id) diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 18a41ca24e3..f55163c26e9 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -15,7 +15,7 @@ describe TodosHelper do end describe '#todo_projects_options' do - let(:projects) { create_list(:empty_project, 3) } + let(:projects) { create_list(:project, 3) } let(:user) { create(:user) } it 'returns users authorised projects in json format' do diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index ad19cf9263d..c3cccbb0d95 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe VisibilityLevelHelper do - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:group) { build(:group) } let(:personal_snippet) { build(:personal_snippet) } let(:project_snippet) { build(:project_snippet) } @@ -60,8 +60,8 @@ describe VisibilityLevelHelper do describe "skip_level?" do describe "forks" do - let(:project) { create(:empty_project, :internal) } - let(:fork_project) { create(:empty_project, forked_from_project: project) } + let(:project) { create(:project, :internal) } + let(:fork_project) { create(:project, forked_from_project: project) } it "skips levels" do expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy @@ -71,7 +71,7 @@ describe VisibilityLevelHelper do end describe "non-forked project" do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } it "skips levels" do expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb index 0877770c167..83283f03940 100644 --- a/spec/initializers/6_validations_spec.rb +++ b/spec/initializers/6_validations_spec.rb @@ -23,6 +23,16 @@ describe '6_validations' do end end + context 'when one of the settings is incorrect' do + before do + mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number' }) + end + + it 'throws an error' do + expect { validate_storages_config }.to raise_error(/failure_count_threshold/) + end + end + context 'with invalid storage names' do before do mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' }) @@ -84,6 +94,17 @@ describe '6_validations' do expect { validate_storages_paths }.not_to raise_error end end + + describe 'inaccessible storage' do + before do + mock_storages('foo' => { 'path' => 'tmp/tests/a/path/that/does/not/exist' }) + end + + it 'passes through with a warning' do + expect(Rails.logger).to receive(:error) + expect { validate_storages_paths }.not_to raise_error + end + end end def mock_storages(storages) diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index ebdabcf93f1..e5ec90cb8f9 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -2,6 +2,17 @@ require 'spec_helper' require_relative '../../config/initializers/1_settings' describe Settings do + describe '#repositories' do + it 'assigns the default failure attributes' do + repository_settings = Gitlab.config.repositories.storages['broken'] + + expect(repository_settings['failure_count_threshold']).to eq(10) + expect(repository_settings['failure_wait_time']).to eq(30) + expect(repository_settings['failure_reset_time']).to eq(1800) + expect(repository_settings['storage_timeout']).to eq(5) + end + end + describe '#host_without_www' do context 'URL with protocol' do it 'returns the host' do diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/abuse_reports_spec.js index 069d857eab6..13cab81dd60 100644 --- a/spec/javascripts/abuse_reports_spec.js +++ b/spec/javascripts/abuse_reports_spec.js @@ -6,10 +6,10 @@ import '~/abuse_reports'; const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; const MAX_MESSAGE_LENGTH = 500; - let messages; + let $messages; const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); - const findMessage = searchText => messages.filter( + const findMessage = searchText => $messages.filter( (index, element) => element.innerText.indexOf(searchText) > -1, ).first(); @@ -18,7 +18,7 @@ import '~/abuse_reports'; beforeEach(function () { loadFixtures(FIXTURE); this.abuseReports = new global.AbuseReports(); - messages = $('.abuse-reports .message'); + $messages = $('.abuse-reports .message'); }); it('should truncate long messages', () => { diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 1518ae68b0d..46e072a8ebb 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -1,4 +1,3 @@ -import '~/extensions/array'; import 'jquery'; import 'jquery-ujs'; import '~/ajax_loading_spinner'; diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js new file mode 100644 index 00000000000..2c8183ff77b --- /dev/null +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -0,0 +1,42 @@ +import 'dropzone'; +import BlobFileDropzone from '~/blob/blob_file_dropzone'; + +describe('BlobFileDropzone', () => { + preloadFixtures('blob/show.html.raw'); + + beforeEach(() => { + loadFixtures('blob/show.html.raw'); + const form = $('.js-upload-blob-form'); + this.blobFileDropzone = new BlobFileDropzone(form, 'POST'); + this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; + this.replaceFileButton = $('#submit-all'); + }); + + describe('submit button', () => { + it('requires file', () => { + spyOn(window, 'alert'); + + this.replaceFileButton.click(); + + expect(window.alert).toHaveBeenCalled(); + }); + + it('is disabled while uploading', () => { + spyOn(window, 'alert'); + + const file = { + name: 'some-file.jpg', + type: 'jpg', + }; + const fakeEvent = jQuery.Event('drop', { + dataTransfer: { files: [file] }, + }); + + this.dropzone.listeners[0].events.drop(fakeEvent); + this.replaceFileButton.click(); + + expect(window.alert).not.toHaveBeenCalled(); + expect(this.replaceFileButton.is(':disabled')).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index af04e7c1e72..cfa6650d85f 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -3,10 +3,10 @@ import BlobViewer from '~/blob/viewer/index'; describe('Blob viewer', () => { let blob; - preloadFixtures('blob/show.html.raw'); + preloadFixtures('snippets/show.html.raw'); beforeEach(() => { - loadFixtures('blob/show.html.raw'); + loadFixtures('snippets/show.html.raw'); $('#modal-upload-blob').remove(); blob = new BlobViewer(); diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index bd9b4fbfdd3..69cfcbbce5a 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -238,12 +238,6 @@ describe('Issue card component', () => { }); describe('labels', () => { - it('does not render any', () => { - expect( - component.$el.querySelector('.label'), - ).toBeNull(); - }); - describe('exists', () => { beforeEach((done) => { component.issue.addLabel(label1); @@ -251,16 +245,21 @@ describe('Issue card component', () => { Vue.nextTick(() => done()); }); - it('does not render list label', () => { + it('renders list label', () => { expect( component.$el.querySelectorAll('.label').length, - ).toBe(1); + ).toBe(2); }); it('renders label', () => { + const nodes = []; + component.$el.querySelectorAll('.label').forEach((label) => { + nodes.push(label.title); + }); + expect( - component.$el.querySelector('.label').textContent, - ).toContain(label1.title); + nodes.includes(label1.description), + ).toBe(true); }); it('sets label description as title', () => { @@ -270,9 +269,14 @@ describe('Issue card component', () => { }); it('sets background color of button', () => { + const nodes = []; + component.$el.querySelectorAll('.label').forEach((label) => { + nodes.push(label.style.backgroundColor); + }); + expect( - component.$el.querySelector('.label').style.backgroundColor, - ).toContain(label1.color); + nodes.includes(label1.color), + ).toBe(true); }); }); }); diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index be90dbdd88a..35149611095 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -5,7 +5,6 @@ import '~/lib/utils/datetime_utility'; import '~/lib/utils/url_utility'; import '~/build'; import '~/breakpoints'; -import 'vendor/jquery.nicescroll'; describe('Build', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; diff --git a/spec/javascripts/droplab/plugins/ajax_spec.js b/spec/javascripts/droplab/plugins/ajax_spec.js new file mode 100644 index 00000000000..085f25764fe --- /dev/null +++ b/spec/javascripts/droplab/plugins/ajax_spec.js @@ -0,0 +1,36 @@ +import AjaxCache from '~/lib/utils/ajax_cache'; +import Ajax from '~/droplab/plugins/ajax'; + +describe('Ajax', () => { + describe('preprocessing', () => { + const config = {}; + + describe('is not configured', () => { + it('passes the data through', () => { + const data = ['data']; + expect(Ajax.preprocessing(config, data)).toEqual(data); + }); + }); + + describe('is configured', () => { + const processedArray = ['processed']; + + beforeEach(() => { + config.preprocessing = () => processedArray; + spyOn(config, 'preprocessing').and.callFake(() => processedArray); + }); + + it('calls preprocessing', () => { + Ajax.preprocessing(config, []); + expect(config.preprocessing.calls.count()).toBe(1); + }); + + it('overrides AjaxCache', () => { + spyOn(AjaxCache, 'override').and.callFake((endpoint, results) => expect(results).toEqual(processedArray)); + + Ajax.preprocessing(config, []); + expect(AjaxCache.override.calls.count()).toBe(1); + }); + }); + }); +}); diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js deleted file mode 100644 index b1b81b4efc2..00000000000 --- a/spec/javascripts/extensions/array_spec.js +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable space-before-function-paren, no-var */ - -import '~/extensions/array'; - -(function() { - describe('Array extensions', function() { - describe('first', function() { - return it('returns the first item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.first()).toBe(0); - }); - }); - describe('last', function() { - return it('returns the last item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.last()).toBe(5); - }); - }); - }); -}).call(window); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index f55726379f3..b1b3d43f241 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -1,4 +1,3 @@ -import '~/extensions/array'; import '~/filtered_search/dropdown_utils'; import '~/filtered_search/filtered_search_tokenizer'; import '~/filtered_search/filtered_search_dropdown_manager'; @@ -191,6 +190,102 @@ describe('Dropdown Utils', () => { }); }); + describe('mergeDuplicateLabels', () => { + const dataMap = { + label: { + title: 'label', + color: '#FFFFFF', + }, + }; + + it('should add label to dataMap if it is not a duplicate', () => { + const newLabel = { + title: 'new-label', + color: '#000000', + }; + + const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, newLabel); + expect(updated[newLabel.title]).toEqual(newLabel); + }); + + it('should merge colors if label is a duplicate', () => { + const duplicate = { + title: 'label', + color: '#000000', + }; + + const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, duplicate); + expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]); + }); + }); + + describe('duplicateLabelColor', () => { + it('should linear-gradient 2 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)'); + }); + + it('should linear-gradient 3 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)'); + }); + + it('should linear-gradient 4 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)'); + }); + + it('should not linear-gradient more than 4 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD', '#EEEEEE']); + expect(gradient.indexOf('#EEEEEE') === -1).toEqual(true); + }); + }); + + describe('duplicateLabelPreprocessing', () => { + it('should set preprocessed to true', () => { + const results = gl.DropdownUtils.duplicateLabelPreprocessing([]); + expect(results.preprocessed).toEqual(true); + }); + + it('should not mutate existing data if there are no duplicates', () => { + const data = [{ + title: 'label1', + color: '#FFFFFF', + }, { + title: 'label2', + color: '#000000', + }]; + const results = gl.DropdownUtils.duplicateLabelPreprocessing(data); + + expect(results.length).toEqual(2); + expect(results[0]).toEqual(data[0]); + expect(results[1]).toEqual(data[1]); + }); + + describe('duplicate labels', () => { + const data = [{ + title: 'label', + color: '#FFFFFF', + }, { + title: 'label', + color: '#000000', + }]; + const results = gl.DropdownUtils.duplicateLabelPreprocessing(data); + + it('should merge duplicate labels', () => { + expect(results.length).toEqual(1); + }); + + it('should convert multiple colored labels into linear-gradient', () => { + expect(results[0].color).toEqual(gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000'])); + }); + + it('should set multiple colored label text color to black', () => { + expect(results[0].text_color).toEqual('#000000'); + }); + }); + }); + describe('setDataValueIfSelected', () => { beforeEach(() => { spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js index 9e2076dc383..5c7e9115aac 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js @@ -1,4 +1,3 @@ -import '~/extensions/array'; import '~/filtered_search/filtered_search_visual_tokens'; import '~/filtered_search/filtered_search_tokenizer'; import '~/filtered_search/filtered_search_dropdown_manager'; diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index 1a7631994b4..69b424c3af5 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -1,4 +1,3 @@ -import '~/extensions/array'; import '~/filtered_search/filtered_search_token_keys'; describe('Filtered Search Token Keys', () => { diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js index e4a15c83c23..585bea9b499 100644 --- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js @@ -1,4 +1,3 @@ -import '~/extensions/array'; import '~/filtered_search/filtered_search_token_keys'; import '~/filtered_search/filtered_search_tokenizer'; diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index fa4343ffbc8..67166802c70 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -797,6 +797,69 @@ describe('Filtered Search Visual Tokens', () => { }); }); + describe('setTokenStyle', () => { + let originalTextColor; + + beforeEach(() => { + originalTextColor = bugLabelToken.style.color; + }); + + it('should set backgroundColor', () => { + const originalBackgroundColor = bugLabelToken.style.backgroundColor; + const token = subject.setTokenStyle(bugLabelToken, 'blue', 'white'); + expect(token.style.backgroundColor).toEqual('blue'); + expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor); + }); + + it('should not set backgroundColor when it is a linear-gradient', () => { + const token = subject.setTokenStyle(bugLabelToken, 'linear-gradient(135deg, red, blue)', 'white'); + expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor); + }); + + it('should set textColor', () => { + const token = subject.setTokenStyle(bugLabelToken, 'white', 'black'); + expect(token.style.color).toEqual('black'); + expect(token.style.color).not.toEqual(originalTextColor); + }); + + it('should add inverted class when textColor is #FFFFFF', () => { + const token = subject.setTokenStyle(bugLabelToken, 'black', '#FFFFFF'); + expect(token.style.color).toEqual('rgb(255, 255, 255)'); + expect(token.style.color).not.toEqual(originalTextColor); + expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true); + }); + }); + + describe('preprocessLabel', () => { + const endpoint = 'endpoint'; + + it('does not preprocess more than once', () => { + let labels = []; + + spyOn(gl.DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []); + + labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + + expect(gl.DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1); + }); + + describe('not preprocessed before', () => { + it('returns preprocessed labels', () => { + let labels = []; + expect(labels.preprocessed).not.toEqual(true); + labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + expect(labels.preprocessed).toEqual(true); + }); + + it('overrides AjaxCache with preprocessed results', () => { + spyOn(AjaxCache, 'override').and.callFake(() => {}); + gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, []); + expect(AjaxCache.override.calls.count()).toEqual(1); + }); + }); + }); + describe('updateLabelTokenColor', () => { const jsonFixtureName = 'labels/project_labels.json'; const dummyEndpoint = '/dummy/endpoint'; diff --git a/spec/javascripts/fixtures/balsamiq.rb b/spec/javascripts/fixtures/balsamiq.rb index b5372821bf5..234e246119a 100644 --- a/spec/javascripts/fixtures/balsamiq.rb +++ b/spec/javascripts/fixtures/balsamiq.rb @@ -4,7 +4,7 @@ describe 'Balsamiq file', '(JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'balsamiq-project') } + let(:project) { create(:project, :repository, namespace: namespace, path: 'balsamiq-project') } before(:all) do clean_frontend_fixtures('blob/balsamiq/') diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb index 16e598a4b29..fca3f5b1bfe 100644 --- a/spec/javascripts/fixtures/deploy_keys.rb +++ b/spec/javascripts/fixtures/deploy_keys.rb @@ -6,7 +6,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control let(:admin) { create(:admin) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') } - let(:project2) { create(:empty_project, :internal)} + let(:project2) { create(:project, :internal)} before(:all) do clean_frontend_fixtures('deploy_keys/') diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 7e2f364ffa4..f9d8b5c569c 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -5,7 +5,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont let(:admin) { create(:admin) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } + let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') } let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) } let(:pipeline) do diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb index ac5b06ace6d..4481a187f63 100644 --- a/spec/javascripts/fixtures/merge_requests_diffs.rb +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -6,7 +6,7 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type let(:admin) { create(:admin) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } + let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') } let(:path) { "files/ruby/popen.rb" } let(:position) do diff --git a/spec/javascripts/fixtures/pdf.rb b/spec/javascripts/fixtures/pdf.rb index 6b2422a7986..ef9976b9fd3 100644 --- a/spec/javascripts/fixtures/pdf.rb +++ b/spec/javascripts/fixtures/pdf.rb @@ -4,7 +4,7 @@ describe 'PDF file', '(JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'pdf-project') } + let(:project) { create(:project, :repository, namespace: namespace, path: 'pdf-project') } before(:all) do clean_frontend_fixtures('blob/pdf/') diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml new file mode 100644 index 00000000000..54bc1a59279 --- /dev/null +++ b/spec/javascripts/fixtures/project_select_combo_button.html.haml @@ -0,0 +1,6 @@ +.project-item-select-holder + %input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } } + %a.new-project-item-link{ data: { label: 'New issue' }, href: ''} + %i.fa.fa-spinner.spin + %a.new-project-item-select-button + %i.fa.fa-caret-down diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 17533443d76..25f5a3b0bb3 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -4,7 +4,7 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'raw-project') } + let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') } before(:all) do clean_frontend_fixtures('blob/notebook/') diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb new file mode 100644 index 00000000000..cc825c82190 --- /dev/null +++ b/spec/javascripts/fixtures/snippet.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe SnippetsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') } + let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) } + + render_views + + before(:all) do + clean_frontend_fixtures('snippets/') + end + + before(:each) do + sign_in(admin) + end + + it 'snippets/show.html.raw' do |example| + get(:show, id: snippet.to_param) + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js new file mode 100644 index 00000000000..ea2a4caffaf --- /dev/null +++ b/spec/javascripts/fly_out_nav_spec.js @@ -0,0 +1,230 @@ +/* global bp */ +import Cookies from 'js-cookie'; +import { + calculateTop, + hideSubLevelItems, + showSubLevelItems, + canShowSubItems, + canShowActiveSubItems, +} from '~/fly_out_nav'; + +describe('Fly out sidebar navigation', () => { + let el; + let breakpointSize = 'lg'; + + beforeEach(() => { + el = document.createElement('div'); + el.style.position = 'relative'; + document.body.appendChild(el); + + spyOn(bp, 'getBreakpointSize').and.callFake(() => breakpointSize); + }); + + afterEach(() => { + el.remove(); + breakpointSize = 'lg'; + }); + + describe('calculateTop', () => { + it('returns boundingRect top', () => { + const boundingRect = { + top: 100, + height: 100, + }; + + expect( + calculateTop(boundingRect, 100), + ).toBe(100); + }); + + it('returns boundingRect - bottomOverflow', () => { + const boundingRect = { + top: window.innerHeight - 50, + height: 100, + }; + + expect( + calculateTop(boundingRect, 100), + ).toBe(window.innerHeight - 50); + }); + }); + + describe('hideSubLevelItems', () => { + beforeEach(() => { + el.innerHTML = '<div class="sidebar-sub-level-items"></div>'; + }); + + it('hides subitems', () => { + hideSubLevelItems(el); + + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).toBe('none'); + }); + + it('does not hude subitems on mobile', () => { + breakpointSize = 'xs'; + + hideSubLevelItems(el); + + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).not.toBe('none'); + }); + + it('removes is-over class', () => { + spyOn(el.classList, 'remove'); + + hideSubLevelItems(el); + + expect( + el.classList.remove, + ).toHaveBeenCalledWith('is-over'); + }); + + it('removes is-above class from sub-items', () => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + + spyOn(subItems.classList, 'remove'); + + hideSubLevelItems(el); + + expect( + subItems.classList.remove, + ).toHaveBeenCalledWith('is-above'); + }); + + it('does nothing if el has no sub-items', () => { + el.innerHTML = ''; + + spyOn(el.classList, 'remove'); + + hideSubLevelItems(el); + + expect( + el.classList.remove, + ).not.toHaveBeenCalledWith(); + }); + }); + + describe('showSubLevelItems', () => { + beforeEach(() => { + el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>'; + }); + + it('adds is-over class to el', () => { + spyOn(el.classList, 'add'); + + showSubLevelItems(el); + + expect( + el.classList.add, + ).toHaveBeenCalledWith('is-over'); + }); + + it('does not show sub-items on mobile', () => { + breakpointSize = 'xs'; + + showSubLevelItems(el); + + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).not.toBe('block'); + }); + + it('does not shows sub-items', () => { + showSubLevelItems(el); + + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).toBe('block'); + }); + + it('sets transform of sub-items', () => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + showSubLevelItems(el); + + expect( + subItems.style.transform, + ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top)}px, 0px)`); + }); + + it('sets is-above when element is above', () => { + const subItems = el.querySelector('.sidebar-sub-level-items'); + subItems.style.height = `${window.innerHeight + el.offsetHeight}px`; + el.style.top = `${window.innerHeight - el.offsetHeight}px`; + + spyOn(subItems.classList, 'add'); + + showSubLevelItems(el); + + expect( + subItems.classList.add, + ).toHaveBeenCalledWith('is-above'); + }); + }); + + describe('canShowSubItems', () => { + it('returns true if on desktop size', () => { + expect( + canShowSubItems(), + ).toBeTruthy(); + }); + + it('returns false if on mobile size', () => { + breakpointSize = 'xs'; + + expect( + canShowSubItems(), + ).toBeFalsy(); + }); + }); + + describe('canShowActiveSubItems', () => { + afterEach(() => { + Cookies.remove('sidebar_collapsed'); + }); + + it('returns true by default', () => { + expect( + canShowActiveSubItems(el), + ).toBeTruthy(); + }); + + it('returns false when cookie is false & element is active', () => { + Cookies.set('sidebar_collapsed', 'false'); + el.classList.add('active'); + + expect( + canShowActiveSubItems(el), + ).toBeFalsy(); + }); + + it('returns true when cookie is false & element is active', () => { + Cookies.set('sidebar_collapsed', 'true'); + el.classList.add('active'); + + expect( + canShowActiveSubItems(el), + ).toBeTruthy(); + }); + + it('returns true when element is active & breakpoint is sm', () => { + breakpointSize = 'sm'; + el.classList.add('active'); + + expect( + canShowActiveSubItems(el), + ).toBeTruthy(); + }); + + it('returns true when element is active & breakpoint is md', () => { + breakpointSize = 'md'; + el.classList.add('active'); + + expect( + canShowActiveSubItems(el), + ).toBeTruthy(); + }); + }); +}); diff --git a/spec/javascripts/groups/group_identicon_spec.js b/spec/javascripts/groups/group_identicon_spec.js new file mode 100644 index 00000000000..66772327503 --- /dev/null +++ b/spec/javascripts/groups/group_identicon_spec.js @@ -0,0 +1,60 @@ +import Vue from 'vue'; +import groupIdenticonComponent from '~/groups/components/group_identicon.vue'; +import GroupsStore from '~/groups/stores/groups_store'; +import { group1 } from './mock_data'; + +const createComponent = () => { + const Component = Vue.extend(groupIdenticonComponent); + const store = new GroupsStore(); + const group = store.decorateGroup(group1); + + return new Component({ + propsData: { + entityId: group.id, + entityName: group.name, + }, + }).$mount(); +}; + +describe('GroupIdenticonComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + describe('computed', () => { + describe('identiconStyles', () => { + it('should return styles attribute value with `background-color` property', () => { + vm.entityId = 4; + + expect(vm.identiconStyles).toBeDefined(); + expect(vm.identiconStyles.indexOf('background-color: #E0F2F1;') > -1).toBeTruthy(); + }); + + it('should return styles attribute value with `color` property', () => { + vm.entityId = 4; + + expect(vm.identiconStyles).toBeDefined(); + expect(vm.identiconStyles.indexOf('color: #555;') > -1).toBeTruthy(); + }); + }); + + describe('identiconTitle', () => { + it('should return first letter of entity title in uppercase', () => { + vm.entityName = 'dummy-group'; + + expect(vm.identiconTitle).toBeDefined(); + expect(vm.identiconTitle).toBe('D'); + }); + }); + }); + + describe('template', () => { + it('should render identicon', () => { + expect(vm.$el.nodeName).toBe('DIV'); + expect(vm.$el.classList.contains('identicon')).toBeTruthy(); + expect(vm.$el.getAttribute('style').indexOf('background-color') > -1).toBeTruthy(); + }); + }); +}); diff --git a/spec/javascripts/groups/groups_spec.js b/spec/javascripts/groups/groups_spec.js index aaffb56fa94..b14153dbbfa 100644 --- a/spec/javascripts/groups/groups_spec.js +++ b/spec/javascripts/groups/groups_spec.js @@ -64,6 +64,19 @@ describe('Groups Component', () => { expect(lists[2].querySelector('#group-1120').textContent).toContain(groups.id1119.subGroups.id1120.name); }); + it('should render group identicon when group avatar is not present', () => { + const avatar = component.$el.querySelector('#group-12 .avatar-container .avatar'); + expect(avatar.nodeName).toBe('DIV'); + expect(avatar.classList.contains('identicon')).toBeTruthy(); + expect(avatar.getAttribute('style').indexOf('background-color') > -1).toBeTruthy(); + }); + + it('should render group avatar when group avatar is present', () => { + const avatar = component.$el.querySelector('#group-1120 .avatar-container .avatar'); + expect(avatar.nodeName).toBe('IMG'); + expect(avatar.classList.contains('identicon')).toBeFalsy(); + }); + it('should remove prefix of parent group', () => { expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4'); }); diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js index b3f5d791b89..5bb84b591f4 100644 --- a/spec/javascripts/groups/mock_data.js +++ b/spec/javascripts/groups/mock_data.js @@ -1,5 +1,5 @@ const group1 = { - id: '12', + id: 12, name: 'level1', path: 'level1', description: 'foo', @@ -71,7 +71,7 @@ const group21 = { path: 'chef', description: 'foo', visibility: 'public', - avatar_url: null, + avatar_url: '/uploads/-/system/group/avatar/2/GitLab.png', web_url: 'http://localhost:3000/groups/devops/chef', group_path: '/devops/chef', full_name: 'devops / chef', diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index c99f379b871..e47adc49224 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -4,7 +4,6 @@ import '~/gl_dropdown'; import 'select2'; -import 'vendor/jquery.nicescroll'; import '~/api'; import '~/create_label'; import '~/issuable_context'; diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js index 2c946802dcd..49971bd91e2 100644 --- a/spec/javascripts/lib/utils/ajax_cache_spec.js +++ b/spec/javascripts/lib/utils/ajax_cache_spec.js @@ -77,6 +77,15 @@ describe('AjaxCache', () => { }); }); + describe('override', () => { + it('overrides existing cache', () => { + AjaxCache.internalStorage.endpoint = 'existing-endpoint'; + AjaxCache.override('endpoint', 'new-endpoint'); + + expect(AjaxCache.internalStorage.endpoint).toEqual('new-endpoint'); + }); + }); + describe('retrieve', () => { let ajaxSpy; diff --git a/spec/javascripts/lib/utils/sticky_spec.js b/spec/javascripts/lib/utils/sticky_spec.js new file mode 100644 index 00000000000..c3ee3ef9825 --- /dev/null +++ b/spec/javascripts/lib/utils/sticky_spec.js @@ -0,0 +1,52 @@ +import { isSticky } from '~/lib/utils/sticky'; + +describe('sticky', () => { + const el = { + offsetTop: 0, + classList: {}, + }; + + beforeEach(() => { + el.offsetTop = 0; + el.classList.add = jasmine.createSpy('spy'); + el.classList.remove = jasmine.createSpy('spy'); + }); + + describe('classList.remove', () => { + it('does not call classList.remove when stuck', () => { + isSticky(el, 0, 0); + + expect( + el.classList.remove, + ).not.toHaveBeenCalled(); + }); + + it('calls classList.remove when not stuck', () => { + el.offsetTop = 10; + isSticky(el, 0, 0); + + expect( + el.classList.remove, + ).toHaveBeenCalledWith('is-stuck'); + }); + }); + + describe('classList.add', () => { + it('calls classList.add when stuck', () => { + isSticky(el, 0, 0); + + expect( + el.classList.add, + ).toHaveBeenCalledWith('is-stuck'); + }); + + it('does not call classList.add when not stuck', () => { + el.offsetTop = 10; + isSticky(el, 0, 0); + + expect( + el.classList.add, + ).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js index f661fae5fe2..bebed432f91 100644 --- a/spec/javascripts/pdf/index_spec.js +++ b/spec/javascripts/pdf/index_spec.js @@ -1,8 +1,8 @@ /* eslint-disable import/no-unresolved */ import Vue from 'vue'; -import { PDFJS } from 'pdfjs-dist'; -import workerSrc from 'vendor/pdf.worker'; +import { PDFJS } from 'vendor/pdf'; +import workerSrc from 'vendor/pdf.worker.min'; import PDFLab from '~/pdf/index.vue'; import pdf from '../fixtures/blob/pdf/test.pdf'; diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js index ac76ebbfbe6..ac5b21e8f6c 100644 --- a/spec/javascripts/pdf/page_spec.js +++ b/spec/javascripts/pdf/page_spec.js @@ -1,8 +1,8 @@ /* eslint-disable import/no-unresolved */ import Vue from 'vue'; -import pdfjsLib from 'pdfjs-dist'; -import workerSrc from 'vendor/pdf.worker'; +import pdfjsLib from 'vendor/pdf'; +import workerSrc from 'vendor/pdf.worker.min'; import PageComponent from '~/pdf/page/index.vue'; import testPDF from '../fixtures/blob/pdf/test.pdf'; diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js new file mode 100644 index 00000000000..e10a5a3bef6 --- /dev/null +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -0,0 +1,105 @@ +import ProjectSelectComboButton from '~/project_select_combo_button'; + +const fixturePath = 'static/project_select_combo_button.html.raw'; + +describe('Project Select Combo Button', function () { + preloadFixtures(fixturePath); + + beforeEach(function () { + this.defaults = { + label: 'Select project to create issue', + groupId: 12345, + projectMeta: { + name: 'My Cool Project', + url: 'http://mycoolproject.com', + }, + newProjectMeta: { + name: 'My Other Cool Project', + url: 'http://myothercoolproject.com', + }, + localStorageKey: 'group-12345-new-issue-recent-project', + relativePath: 'issues/new', + }; + + loadFixtures(fixturePath); + + this.newItemBtn = document.querySelector('.new-project-item-link'); + this.projectSelectInput = document.querySelector('.project-item-select'); + }); + + describe('on page load when localStorage is empty', function () { + beforeEach(function () { + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + }); + + it('newItemBtn is disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(true); + expect(this.newItemBtn.classList.contains('disabled')).toBe(true); + }); + + it('newItemBtn href is null', function () { + expect(this.newItemBtn.getAttribute('href')).toBe(''); + }); + + it('newItemBtn text is the plain default label', function () { + expect(this.newItemBtn.textContent).toBe(this.defaults.label); + }); + }); + + describe('on page load when localStorage is filled', function () { + beforeEach(function () { + window.localStorage + .setItem(this.defaults.localStorageKey, JSON.stringify(this.defaults.projectMeta)); + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + }); + + it('newItemBtn is not disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(false); + expect(this.newItemBtn.classList.contains('disabled')).toBe(false); + }); + + it('newItemBtn href is correctly set', function () { + expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url); + }); + + it('newItemBtn text is the cached label', function () { + expect(this.newItemBtn.textContent) + .toBe(`New issue in ${this.defaults.projectMeta.name}`); + }); + + afterEach(function () { + window.localStorage.clear(); + }); + }); + + describe('after selecting a new project', function () { + beforeEach(function () { + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + + // mock the effect of selecting an item from the projects dropdown (select2) + $('.project-item-select') + .val(JSON.stringify(this.defaults.newProjectMeta)) + .trigger('change'); + }); + + it('newItemBtn is not disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(false); + expect(this.newItemBtn.classList.contains('disabled')).toBe(false); + }); + + it('newItemBtn href is correctly set', function () { + expect(this.newItemBtn.getAttribute('href')) + .toBe('http://myothercoolproject.com/issues/new'); + }); + + it('newItemBtn text is the selected project label', function () { + expect(this.newItemBtn.textContent) + .toBe(`New issue in ${this.defaults.newProjectMeta.name}`); + }); + + afterEach(function () { + window.localStorage.clear(); + }); + }); +}); + diff --git a/spec/javascripts/projects/project_import_gitlab_project_spec.js b/spec/javascripts/projects/project_import_gitlab_project_spec.js new file mode 100644 index 00000000000..2f1aae109e3 --- /dev/null +++ b/spec/javascripts/projects/project_import_gitlab_project_spec.js @@ -0,0 +1,25 @@ +import projectImportGitlab from '~/projects/project_import_gitlab_project'; + +describe('Import Gitlab project', () => { + let projectName; + beforeEach(() => { + projectName = 'project'; + window.history.pushState({}, null, `?path=${projectName}`); + + setFixtures(` + <input class="js-path-name" /> + `); + + projectImportGitlab.bindEvents(); + }); + + afterEach(() => { + window.history.pushState({}, null, ''); + }); + + describe('path name', () => { + it('should fill in the project name derived from the previously filled project name', () => { + expect(document.querySelector('.js-path-name').value).toEqual(projectName); + }); + }); +}); diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js new file mode 100644 index 00000000000..850768f0e4f --- /dev/null +++ b/spec/javascripts/projects/project_new_spec.js @@ -0,0 +1,127 @@ +import projectNew from '~/projects/project_new'; + +describe('New Project', () => { + let $projectImportUrl; + let $projectPath; + + beforeEach(() => { + setFixtures(` + <input id="project_import_url" /> + <input id="project_path" /> + `); + + $projectImportUrl = $('#project_import_url'); + $projectPath = $('#project_path'); + }); + + describe('deriveProjectPathFromUrl', () => { + const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`; + + beforeEach(() => { + projectNew.bindEvents(); + $projectPath.val('').keyup().val(dummyImportUrl); + }); + + it('does not change project path for disabled $projectImportUrl', () => { + $projectImportUrl.attr('disabled', true); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual(dummyImportUrl); + }); + + describe('for enabled $projectImportUrl', () => { + beforeEach(() => { + $projectImportUrl.attr('disabled', false); + }); + + it('does not change project path if it is set by user', () => { + $projectPath.keyup(); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual(dummyImportUrl); + }); + + it('does not change project path for empty $projectImportUrl', () => { + $projectImportUrl.val(''); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual(dummyImportUrl); + }); + + it('does not change project path for whitespace $projectImportUrl', () => { + $projectImportUrl.val(' '); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual(dummyImportUrl); + }); + + it('does not change project path for $projectImportUrl without slashes', () => { + $projectImportUrl.val('has-no-slash'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual(dummyImportUrl); + }); + + it('changes project path to last $projectImportUrl component', () => { + $projectImportUrl.val('/this/is/last'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('last'); + }); + + it('ignores trailing slashes in $projectImportUrl', () => { + $projectImportUrl.val('/has/trailing/slash/'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('slash'); + }); + + it('ignores fragment identifier in $projectImportUrl', () => { + $projectImportUrl.val('/this/has/a#fragment-identifier/'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('a'); + }); + + it('ignores query string in $projectImportUrl', () => { + $projectImportUrl.val('/url/with?query=string'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('with'); + }); + + it('ignores trailing .git in $projectImportUrl', () => { + $projectImportUrl.val('/repository.git'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('repository'); + }); + + it('changes project path for HTTPS URL in $projectImportUrl', () => { + $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('project'); + }); + + it('changes project path for SSH URL in $projectImportUrl', () => { + $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git'); + + projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + + expect($projectPath.val()).toEqual('gitlab-ce'); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js new file mode 100644 index 00000000000..db2b7d51626 --- /dev/null +++ b/spec/javascripts/repo/components/repo_commit_section_spec.js @@ -0,0 +1,158 @@ +import Vue from 'vue'; +import repoCommitSection from '~/repo/components/repo_commit_section.vue'; +import RepoStore from '~/repo/stores/repo_store'; +import RepoHelper from '~/repo/helpers/repo_helper'; +import Api from '~/api'; + +describe('RepoCommitSection', () => { + const branch = 'master'; + const projectUrl = 'projectUrl'; + const openedFiles = [{ + id: 0, + changed: true, + url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`, + newContent: 'a', + }, { + id: 1, + changed: true, + url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`, + newContent: 'b', + }, { + id: 2, + url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`, + changed: false, + }]; + + RepoStore.projectUrl = projectUrl; + + function createComponent() { + const RepoCommitSection = Vue.extend(repoCommitSection); + + return new RepoCommitSection().$mount(); + } + + it('renders a commit section', () => { + RepoStore.isCommitable = true; + RepoStore.targetBranch = branch; + RepoStore.openedFiles = openedFiles; + + spyOn(RepoHelper, 'getBranch').and.returnValue(branch); + + const vm = createComponent(); + const changedFiles = [...vm.$el.querySelectorAll('.changed-files > li')]; + const commitMessage = vm.$el.querySelector('#commit-message'); + const submitCommit = vm.$el.querySelector('.submit-commit'); + const targetBranch = vm.$el.querySelector('.target-branch'); + + expect(vm.$el.querySelector(':scope > form')).toBeTruthy(); + expect(vm.$el.querySelector('.staged-files').textContent).toEqual('Staged files (2)'); + expect(changedFiles.length).toEqual(2); + + changedFiles.forEach((changedFile, i) => { + const filePath = RepoHelper.getFilePathFromFullPath(openedFiles[i].url, branch); + + expect(changedFile.textContent).toEqual(filePath); + }); + + expect(commitMessage.tagName).toEqual('TEXTAREA'); + expect(commitMessage.name).toEqual('commit-message'); + expect(submitCommit.type).toEqual('submit'); + expect(submitCommit.disabled).toBeTruthy(); + expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy(); + expect(vm.$el.querySelector('.commit-summary').textContent).toEqual('Commit 2 files'); + expect(targetBranch.querySelector(':scope > label').textContent).toEqual('Target branch'); + expect(targetBranch.querySelector('.help-block').textContent).toEqual(branch); + }); + + it('does not render if not isCommitable', () => { + RepoStore.isCommitable = false; + RepoStore.openedFiles = [{ + id: 0, + changed: true, + }]; + + const vm = createComponent(); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); + + it('does not render if no changedFiles', () => { + RepoStore.isCommitable = true; + RepoStore.openedFiles = []; + + const vm = createComponent(); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); + + it('shows commit submit and summary if commitMessage and spinner if submitCommitsLoading', (done) => { + const projectId = 'projectId'; + const commitMessage = 'commitMessage'; + RepoStore.isCommitable = true; + RepoStore.openedFiles = openedFiles; + RepoStore.projectId = projectId; + + spyOn(RepoHelper, 'getBranch').and.returnValue(branch); + + const vm = createComponent(); + const commitMessageEl = vm.$el.querySelector('#commit-message'); + const submitCommit = vm.$el.querySelector('.submit-commit'); + + vm.commitMessage = commitMessage; + + Vue.nextTick(() => { + expect(commitMessageEl.value).toBe(commitMessage); + expect(submitCommit.disabled).toBeFalsy(); + + spyOn(vm, 'makeCommit').and.callThrough(); + spyOn(Api, 'commitMultiple'); + + submitCommit.click(); + + Vue.nextTick(() => { + expect(vm.makeCommit).toHaveBeenCalled(); + expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeTruthy(); + + const args = Api.commitMultiple.calls.allArgs()[0]; + const { commit_message, actions, branch: payloadBranch } = args[1]; + + expect(args[0]).toBe(projectId); + expect(commit_message).toBe(commitMessage); + expect(actions.length).toEqual(2); + expect(payloadBranch).toEqual(branch); + expect(actions[0].action).toEqual('update'); + expect(actions[1].action).toEqual('update'); + expect(actions[0].content).toEqual(openedFiles[0].newContent); + expect(actions[1].content).toEqual(openedFiles[1].newContent); + expect(actions[0].file_path) + .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[0].url, branch)); + expect(actions[1].file_path) + .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[1].url, branch)); + + done(); + }); + }); + }); + + describe('methods', () => { + describe('resetCommitState', () => { + it('should reset store vars and scroll to top', () => { + const vm = { + submitCommitsLoading: true, + changedFiles: new Array(10), + openedFiles: new Array(10), + commitMessage: 'commitMessage', + editMode: true, + }; + + repoCommitSection.methods.resetCommitState.call(vm); + + expect(vm.submitCommitsLoading).toEqual(false); + expect(vm.changedFiles).toEqual([]); + expect(vm.openedFiles).toEqual([]); + expect(vm.commitMessage).toEqual(''); + expect(vm.editMode).toEqual(false); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js new file mode 100644 index 00000000000..df2f9697acc --- /dev/null +++ b/spec/javascripts/repo/components/repo_edit_button_spec.js @@ -0,0 +1,51 @@ +import Vue from 'vue'; +import repoEditButton from '~/repo/components/repo_edit_button.vue'; +import RepoStore from '~/repo/stores/repo_store'; + +describe('RepoEditButton', () => { + function createComponent() { + const RepoEditButton = Vue.extend(repoEditButton); + + return new RepoEditButton().$mount(); + } + + it('renders an edit button that toggles the view state', (done) => { + RepoStore.isCommitable = true; + RepoStore.changedFiles = []; + + const vm = createComponent(); + + expect(vm.$el.tagName).toEqual('BUTTON'); + expect(vm.$el.textContent).toMatch('Edit'); + + spyOn(vm, 'editClicked').and.callThrough(); + + vm.$el.click(); + + Vue.nextTick(() => { + expect(vm.editClicked).toHaveBeenCalled(); + expect(vm.$el.textContent).toMatch('Cancel edit'); + done(); + }); + }); + + it('does not render if not isCommitable', () => { + RepoStore.isCommitable = false; + + const vm = createComponent(); + + expect(vm.$el.innerHTML).toBeUndefined(); + }); + + describe('methods', () => { + describe('editClicked', () => { + it('sets dialog to open when there are changedFiles', () => { + + }); + + it('toggles editMode and calls toggleBlobView', () => { + + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js new file mode 100644 index 00000000000..35e0c995163 --- /dev/null +++ b/spec/javascripts/repo/components/repo_editor_spec.js @@ -0,0 +1,26 @@ +import Vue from 'vue'; +import repoEditor from '~/repo/components/repo_editor.vue'; +import RepoStore from '~/repo/stores/repo_store'; + +describe('RepoEditor', () => { + function createComponent() { + const RepoEditor = Vue.extend(repoEditor); + + return new RepoEditor().$mount(); + } + + it('renders an ide container', () => { + const monacoInstance = jasmine.createSpyObj('monacoInstance', ['onMouseUp', 'onKeyUp', 'setModel', 'updateOptions']); + const monaco = { + editor: jasmine.createSpyObj('editor', ['create']), + }; + RepoStore.monaco = monaco; + + monaco.editor.create.and.returnValue(monacoInstance); + spyOn(repoEditor.watch, 'blobRaw'); + + const vm = createComponent(); + + expect(vm.$el.id).toEqual('ide'); + }); +}); diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js new file mode 100644 index 00000000000..e1f25e4485f --- /dev/null +++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js @@ -0,0 +1,82 @@ +import Vue from 'vue'; +import repoFileButtons from '~/repo/components/repo_file_buttons.vue'; +import RepoStore from '~/repo/stores/repo_store'; + +describe('RepoFileButtons', () => { + function createComponent() { + const RepoFileButtons = Vue.extend(repoFileButtons); + + return new RepoFileButtons().$mount(); + } + + it('renders Raw, Blame, History, Permalink and Preview toggle', () => { + const activeFile = { + extension: 'md', + url: 'url', + raw_path: 'raw_path', + blame_path: 'blame_path', + commits_path: 'commits_path', + permalink: 'permalink', + }; + const activeFileLabel = 'activeFileLabel'; + RepoStore.openedFiles = new Array(1); + RepoStore.activeFile = activeFile; + RepoStore.activeFileLabel = activeFileLabel; + RepoStore.editMode = true; + + const vm = createComponent(); + const raw = vm.$el.querySelector('.raw'); + const blame = vm.$el.querySelector('.blame'); + const history = vm.$el.querySelector('.history'); + + expect(vm.$el.id).toEqual('repo-file-buttons'); + expect(raw.href).toMatch(`/${activeFile.raw_path}`); + expect(raw.textContent).toEqual('Raw'); + expect(blame.href).toMatch(`/${activeFile.blame_path}`); + expect(blame.textContent).toEqual('Blame'); + expect(history.href).toMatch(`/${activeFile.commits_path}`); + expect(history.textContent).toEqual('History'); + expect(vm.$el.querySelector('.permalink').textContent).toEqual('Permalink'); + expect(vm.$el.querySelector('.preview').textContent).toEqual(activeFileLabel); + }); + + it('triggers rawPreviewToggle on preview click', () => { + const activeFile = { + extension: 'md', + url: 'url', + }; + RepoStore.openedFiles = new Array(1); + RepoStore.activeFile = activeFile; + RepoStore.editMode = true; + + const vm = createComponent(); + const preview = vm.$el.querySelector('.preview'); + + spyOn(vm, 'rawPreviewToggle'); + + preview.click(); + + expect(vm.rawPreviewToggle).toHaveBeenCalled(); + }); + + it('does not render preview toggle if not canPreview', () => { + const activeFile = { + extension: 'abcd', + url: 'url', + }; + RepoStore.openedFiles = new Array(1); + RepoStore.activeFile = activeFile; + + const vm = createComponent(); + + expect(vm.$el.querySelector('.preview')).toBeFalsy(); + }); + + it('does not render if not isMini', () => { + RepoStore.openedFiles = []; + + const vm = createComponent(); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/repo/components/repo_file_options_spec.js b/spec/javascripts/repo/components/repo_file_options_spec.js new file mode 100644 index 00000000000..9759b4bf12d --- /dev/null +++ b/spec/javascripts/repo/components/repo_file_options_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import repoFileOptions from '~/repo/components/repo_file_options.vue'; + +describe('RepoFileOptions', () => { + const projectName = 'projectName'; + + function createComponent(propsData) { + const RepoFileOptions = Vue.extend(repoFileOptions); + + return new RepoFileOptions({ + propsData, + }).$mount(); + } + + it('renders the title and new file/folder buttons if isMini is true', () => { + const vm = createComponent({ + isMini: true, + projectName, + }); + + expect(vm.$el.classList.contains('repo-file-options')).toBeTruthy(); + expect(vm.$el.querySelector('.title').textContent).toEqual(projectName); + }); + + it('does not render if isMini is false', () => { + const vm = createComponent({ + isMini: false, + projectName, + }); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js new file mode 100644 index 00000000000..90616ae13ca --- /dev/null +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -0,0 +1,136 @@ +import Vue from 'vue'; +import repoFile from '~/repo/components/repo_file.vue'; + +describe('RepoFile', () => { + const updated = 'updated'; + const file = { + icon: 'icon', + url: 'url', + name: 'name', + lastCommitMessage: 'message', + lastCommitUpdate: Date.now(), + level: 10, + }; + const activeFile = { + url: 'url', + }; + + function createComponent(propsData) { + const RepoFile = Vue.extend(repoFile); + + return new RepoFile({ + propsData, + }).$mount(); + } + + beforeEach(() => { + spyOn(repoFile.mixins[0].methods, 'timeFormated').and.returnValue(updated); + }); + + it('renders link, icon, name and last commit details', () => { + const vm = createComponent({ + file, + activeFile, + }); + const name = vm.$el.querySelector('.repo-file-name'); + const fileIcon = vm.$el.querySelector('.file-icon'); + + expect(vm.$el.classList.contains('active')).toBeTruthy(); + expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px'); + expect(name.title).toEqual(file.url); + expect(name.href).toMatch(`/${file.url}`); + expect(name.textContent).toEqual(file.name); + expect(vm.$el.querySelector('.commit-message').textContent).toBe(file.lastCommitMessage); + expect(vm.$el.querySelector('.commit-update').textContent).toBe(updated); + expect(fileIcon.classList.contains(file.icon)).toBeTruthy(); + expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`); + }); + + it('does render if hasFiles is true and is loading tree', () => { + const vm = createComponent({ + file, + activeFile, + loading: { + tree: true, + }, + hasFiles: true, + }); + + expect(vm.$el.innerHTML).toBeTruthy(); + expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); + }); + + it('renders a spinner if the file is loading', () => { + file.loading = true; + const vm = createComponent({ + file, + activeFile, + loading: { + tree: true, + }, + hasFiles: true, + }); + + expect(vm.$el.innerHTML).toBeTruthy(); + expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${file.level * 10}px`); + }); + + it('does not render if loading tree', () => { + const vm = createComponent({ + file, + activeFile, + loading: { + tree: true, + }, + }); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); + + it('does not render commit message and datetime if mini', () => { + const vm = createComponent({ + file, + activeFile, + isMini: true, + }); + + expect(vm.$el.querySelector('.commit-message')).toBeFalsy(); + expect(vm.$el.querySelector('.commit-update')).toBeFalsy(); + }); + + it('does not set active class if file is active file', () => { + const vm = createComponent({ + file, + activeFile: {}, + }); + + expect(vm.$el.classList.contains('active')).toBeFalsy(); + }); + + it('fires linkClicked when the link is clicked', () => { + const vm = createComponent({ + file, + activeFile, + }); + + spyOn(vm, 'linkClicked'); + + vm.$el.querySelector('.repo-file-name').click(); + + expect(vm.linkClicked).toHaveBeenCalledWith(file); + }); + + describe('methods', () => { + describe('linkClicked', () => { + const vm = jasmine.createSpyObj('vm', ['$emit']); + + it('$emits linkclicked with file obj', () => { + const theFile = {}; + + repoFile.methods.linkClicked.call(vm, theFile); + + expect(vm.$emit).toHaveBeenCalledWith('linkclicked', theFile); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js new file mode 100644 index 00000000000..d84f4c5609e --- /dev/null +++ b/spec/javascripts/repo/components/repo_loading_file_spec.js @@ -0,0 +1,79 @@ +import Vue from 'vue'; +import repoLoadingFile from '~/repo/components/repo_loading_file.vue'; + +describe('RepoLoadingFile', () => { + function createComponent(propsData) { + const RepoLoadingFile = Vue.extend(repoLoadingFile); + + return new RepoLoadingFile({ + propsData, + }).$mount(); + } + + function assertLines(lines) { + lines.forEach((line, n) => { + const index = n + 1; + expect(line.classList.contains(`line-of-code-${index}`)).toBeTruthy(); + }); + } + + function assertColumns(columns) { + columns.forEach((column) => { + const container = column.querySelector('.animation-container'); + const lines = [...container.querySelectorAll(':scope > div')]; + + expect(container).toBeTruthy(); + expect(lines.length).toEqual(6); + assertLines(lines); + }); + } + + it('renders 3 columns of animated LoC', () => { + const vm = createComponent({ + loading: { + tree: true, + }, + hasFiles: false, + }); + const columns = [...vm.$el.querySelectorAll('td')]; + + expect(columns.length).toEqual(3); + assertColumns(columns); + }); + + it('renders 1 column of animated LoC if isMini', () => { + const vm = createComponent({ + loading: { + tree: true, + }, + hasFiles: false, + isMini: true, + }); + const columns = [...vm.$el.querySelectorAll('td')]; + + expect(columns.length).toEqual(1); + assertColumns(columns); + }); + + it('does not render if tree is not loading', () => { + const vm = createComponent({ + loading: { + tree: false, + }, + hasFiles: false, + }); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); + + it('does not render if hasFiles is true', () => { + const vm = createComponent({ + loading: { + tree: true, + }, + hasFiles: true, + }); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js new file mode 100644 index 00000000000..34dde545e6a --- /dev/null +++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; +import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue'; + +describe('RepoPrevDirectory', () => { + function createComponent(propsData) { + const RepoPrevDirectory = Vue.extend(repoPrevDirectory); + + return new RepoPrevDirectory({ + propsData, + }).$mount(); + } + + it('renders a prev dir link', () => { + const prevUrl = 'prevUrl'; + const vm = createComponent({ + prevUrl, + }); + const link = vm.$el.querySelector('a'); + + spyOn(vm, 'linkClicked'); + + expect(link.href).toMatch(`/${prevUrl}`); + expect(link.textContent).toEqual('..'); + + link.click(); + + expect(vm.linkClicked).toHaveBeenCalledWith(prevUrl); + }); + + describe('methods', () => { + describe('linkClicked', () => { + const vm = jasmine.createSpyObj('vm', ['$emit']); + + it('$emits linkclicked with file obj', () => { + const file = {}; + + repoPrevDirectory.methods.linkClicked.call(vm, file); + + expect(vm.$emit).toHaveBeenCalledWith('linkclicked', file); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js new file mode 100644 index 00000000000..4920cf02083 --- /dev/null +++ b/spec/javascripts/repo/components/repo_preview_spec.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import repoPreview from '~/repo/components/repo_preview.vue'; +import RepoStore from '~/repo/stores/repo_store'; + +describe('RepoPreview', () => { + function createComponent() { + const RepoPreview = Vue.extend(repoPreview); + + return new RepoPreview().$mount(); + } + + it('renders a div with the activeFile html', () => { + const activeFile = { + html: '<p class="file-content">html</p>', + }; + RepoStore.activeFile = activeFile; + + const vm = createComponent(); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.innerHTML).toContain(activeFile.html); + }); +}); diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js new file mode 100644 index 00000000000..0d216c9c026 --- /dev/null +++ b/spec/javascripts/repo/components/repo_sidebar_spec.js @@ -0,0 +1,61 @@ +import Vue from 'vue'; +import RepoStore from '~/repo/stores/repo_store'; +import repoSidebar from '~/repo/components/repo_sidebar.vue'; + +describe('RepoSidebar', () => { + function createComponent() { + const RepoSidebar = Vue.extend(repoSidebar); + + return new RepoSidebar().$mount(); + } + + it('renders a sidebar', () => { + RepoStore.files = [{ + id: 0, + }]; + const vm = createComponent(); + const thead = vm.$el.querySelector('thead'); + const tbody = vm.$el.querySelector('tbody'); + + expect(vm.$el.id).toEqual('sidebar'); + expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); + expect(thead.querySelector('.name').textContent).toEqual('Name'); + expect(thead.querySelector('.last-commit').textContent).toEqual('Last Commit'); + expect(thead.querySelector('.last-update').textContent).toEqual('Last Update'); + expect(tbody.querySelector('.repo-file-options')).toBeFalsy(); + expect(tbody.querySelector('.prev-directory')).toBeFalsy(); + expect(tbody.querySelector('.loading-file')).toBeFalsy(); + expect(tbody.querySelector('.file')).toBeTruthy(); + }); + + it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', () => { + RepoStore.openedFiles = [{ + id: 0, + }]; + const vm = createComponent(); + + expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy(); + expect(vm.$el.querySelector('thead')).toBeFalsy(); + expect(vm.$el.querySelector('tbody .repo-file-options')).toBeTruthy(); + }); + + it('renders 5 loading files if tree is loading and not hasFiles', () => { + RepoStore.loading = { + tree: true, + }; + RepoStore.files = []; + const vm = createComponent(); + + expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); + }); + + it('renders a prev directory if isRoot', () => { + RepoStore.files = [{ + id: 0, + }]; + RepoStore.isRoot = true; + const vm = createComponent(); + + expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); + }); +}); diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js new file mode 100644 index 00000000000..f3572804b4a --- /dev/null +++ b/spec/javascripts/repo/components/repo_tab_spec.js @@ -0,0 +1,88 @@ +import Vue from 'vue'; +import repoTab from '~/repo/components/repo_tab.vue'; + +describe('RepoTab', () => { + function createComponent(propsData) { + const RepoTab = Vue.extend(repoTab); + + return new RepoTab({ + propsData, + }).$mount(); + } + + it('renders a close link and a name link', () => { + const tab = { + loading: false, + url: 'url', + name: 'name', + }; + const vm = createComponent({ + tab, + }); + const close = vm.$el.querySelector('.close'); + const name = vm.$el.querySelector(`a[title="${tab.url}"]`); + + spyOn(vm, 'xClicked'); + spyOn(vm, 'tabClicked'); + + expect(close.querySelector('.fa-times')).toBeTruthy(); + expect(name.textContent).toEqual(tab.name); + + close.click(); + name.click(); + + expect(vm.xClicked).toHaveBeenCalledWith(tab); + expect(vm.tabClicked).toHaveBeenCalledWith(tab); + }); + + it('renders a spinner if tab is loading', () => { + const tab = { + loading: true, + url: 'url', + }; + const vm = createComponent({ + tab, + }); + const close = vm.$el.querySelector('.close'); + const name = vm.$el.querySelector(`a[title="${tab.url}"]`); + + expect(close).toBeFalsy(); + expect(name).toBeFalsy(); + expect(vm.$el.querySelector('.fa.fa-spinner.fa-spin')).toBeTruthy(); + }); + + it('renders an fa-circle icon if tab is changed', () => { + const tab = { + loading: false, + url: 'url', + name: 'name', + changed: true, + }; + const vm = createComponent({ + tab, + }); + + expect(vm.$el.querySelector('.close .fa-circle')).toBeTruthy(); + }); + + describe('methods', () => { + describe('xClicked', () => { + const vm = jasmine.createSpyObj('vm', ['$emit']); + + it('returns undefined and does not $emit if file is changed', () => { + const file = { changed: true }; + const returnVal = repoTab.methods.xClicked.call(vm, file); + + expect(returnVal).toBeUndefined(); + expect(vm.$emit).not.toHaveBeenCalled(); + }); + + it('$emits xclicked event with file obj', () => { + const file = { changed: false }; + repoTab.methods.xClicked.call(vm, file); + + expect(vm.$emit).toHaveBeenCalledWith('xclicked', file); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js new file mode 100644 index 00000000000..fdb12cfc00f --- /dev/null +++ b/spec/javascripts/repo/components/repo_tabs_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import RepoStore from '~/repo/stores/repo_store'; +import repoTabs from '~/repo/components/repo_tabs.vue'; + +describe('RepoTabs', () => { + const openedFiles = [{ + id: 0, + active: true, + }, { + id: 1, + }]; + + function createComponent() { + const RepoTabs = Vue.extend(repoTabs); + + return new RepoTabs().$mount(); + } + + it('renders a list of tabs', () => { + RepoStore.openedFiles = openedFiles; + RepoStore.tabsOverflow = true; + + const vm = createComponent(); + const tabs = [...vm.$el.querySelectorAll(':scope > li')]; + + expect(vm.$el.id).toEqual('tabs'); + expect(vm.$el.classList.contains('overflown')).toBeTruthy(); + expect(tabs.length).toEqual(3); + expect(tabs[0].classList.contains('active')).toBeTruthy(); + expect(tabs[1].classList.contains('active')).toBeFalsy(); + expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy(); + }); + + it('does not render a tabs list if not isMini', () => { + RepoStore.openedFiles = []; + + const vm = createComponent(); + + expect(vm.$el.innerHTML).toBeFalsy(); + }); + + it('does not apply overflown class if not tabsOverflow', () => { + RepoStore.openedFiles = openedFiles; + RepoStore.tabsOverflow = false; + + const vm = createComponent(); + + expect(vm.$el.classList.contains('overflown')).toBeFalsy(); + }); + + describe('methods', () => { + describe('xClicked', () => { + it('calls removeFromOpenedFiles with file obj', () => { + const file = {}; + + spyOn(RepoStore, 'removeFromOpenedFiles'); + + repoTabs.methods.xClicked(file); + + expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js new file mode 100644 index 00000000000..be6e779c50f --- /dev/null +++ b/spec/javascripts/repo/monaco_loader_spec.js @@ -0,0 +1,17 @@ +/* global __webpack_public_path__ */ +import monacoContext from 'monaco-editor/dev/vs/loader'; + +describe('MonacoLoader', () => { + it('calls require.config and exports require', () => { + spyOn(monacoContext.require, 'config'); + + const monacoLoader = require('~/repo/monaco_loader'); // eslint-disable-line global-require + + expect(monacoContext.require.config).toHaveBeenCalledWith({ + paths: { + vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase + }, + }); + expect(monacoLoader.default).toBe(monacoContext.require); + }); +}); diff --git a/spec/javascripts/repo/services/repo_service_spec.js b/spec/javascripts/repo/services/repo_service_spec.js new file mode 100644 index 00000000000..d74e6a67b1e --- /dev/null +++ b/spec/javascripts/repo/services/repo_service_spec.js @@ -0,0 +1,121 @@ +import axios from 'axios'; +import RepoService from '~/repo/services/repo_service'; + +describe('RepoService', () => { + it('has default json format param', () => { + expect(RepoService.options.params.format).toBe('json'); + }); + + describe('buildParams', () => { + let newParams; + const url = 'url'; + + beforeEach(() => { + newParams = {}; + + spyOn(Object, 'assign').and.returnValue(newParams); + }); + + it('clones params', () => { + const params = RepoService.buildParams(url); + + expect(Object.assign).toHaveBeenCalledWith({}, RepoService.options.params); + + expect(params).toBe(newParams); + }); + + it('sets and returns viewer params to richif urlIsRichBlob is true', () => { + spyOn(RepoService, 'urlIsRichBlob').and.returnValue(true); + + const params = RepoService.buildParams(url); + + expect(params.viewer).toEqual('rich'); + }); + + it('returns params urlIsRichBlob is false', () => { + spyOn(RepoService, 'urlIsRichBlob').and.returnValue(false); + + const params = RepoService.buildParams(url); + + expect(params.viewer).toBeUndefined(); + }); + + it('calls urlIsRichBlob with the objects url prop if no url arg is provided', () => { + spyOn(RepoService, 'urlIsRichBlob'); + RepoService.url = url; + + RepoService.buildParams(); + + expect(RepoService.urlIsRichBlob).toHaveBeenCalledWith(url); + }); + }); + + describe('urlIsRichBlob', () => { + it('returns true for md extension', () => { + const isRichBlob = RepoService.urlIsRichBlob('url.md'); + + expect(isRichBlob).toBeTruthy(); + }); + + it('returns false for js extension', () => { + const isRichBlob = RepoService.urlIsRichBlob('url.js'); + + expect(isRichBlob).toBeFalsy(); + }); + }); + + describe('getContent', () => { + const params = {}; + const url = 'url'; + const requestPromise = Promise.resolve(); + + beforeEach(() => { + spyOn(RepoService, 'buildParams').and.returnValue(params); + spyOn(axios, 'get').and.returnValue(requestPromise); + }); + + it('calls buildParams and axios.get', () => { + const request = RepoService.getContent(url); + + expect(RepoService.buildParams).toHaveBeenCalledWith(url); + expect(axios.get).toHaveBeenCalledWith(url, { + params, + }); + expect(request).toBe(requestPromise); + }); + + it('uses object url prop if no url arg is provided', () => { + RepoService.url = url; + + RepoService.getContent(); + + expect(axios.get).toHaveBeenCalledWith(url, { + params, + }); + }); + }); + + describe('getBase64Content', () => { + const url = 'url'; + const response = { data: 'data' }; + + beforeEach(() => { + spyOn(RepoService, 'bufferToBase64'); + spyOn(axios, 'get').and.returnValue(Promise.resolve(response)); + }); + + it('calls axios.get and bufferToBase64 on completion', (done) => { + const request = RepoService.getBase64Content(url); + + expect(axios.get).toHaveBeenCalledWith(url, { + responseType: 'arraybuffer', + }); + expect(request).toEqual(jasmine.any(Promise)); + + request.then(() => { + expect(RepoService.bufferToBase64).toHaveBeenCalledWith(response.data); + done(); + }).catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js new file mode 100644 index 00000000000..482be466aad --- /dev/null +++ b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue'; + +describe('Edit Form Buttons', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editFormButtons); + const toggleForm = () => { }; + const updateConfidentialAttribute = () => { }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on or off text based on confidentiality', () => { + expect( + vm1.$el.innerHTML.includes('Turn Off'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('Turn On'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js new file mode 100644 index 00000000000..724f5126945 --- /dev/null +++ b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import editForm from '~/sidebar/components/confidential/edit_form.vue'; + +describe('Edit Form Dropdown', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editForm); + const toggleForm = () => { }; + const updateConfidentialAttribute = () => { }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on the appropriate warning text', () => { + expect( + vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js new file mode 100644 index 00000000000..90eac1ed1ab --- /dev/null +++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js @@ -0,0 +1,65 @@ +import Vue from 'vue'; +import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue'; + +describe('Confidential Issue Sidebar Block', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(confidentialIssueSidebar); + const service = { + update: () => new Promise((resolve, reject) => { + resolve(true); + reject('failed!'); + }), + }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + isEditable: true, + service, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + isEditable: false, + service, + }, + }).$mount(); + }); + + it('shows if confidential and/or editable', () => { + expect( + vm1.$el.innerHTML.includes('Edit'), + ).toBe(true); + + expect( + vm1.$el.innerHTML.includes('This issue is confidential'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('None'), + ).toBe(true); + }); + + it('displays the edit form when editable', (done) => { + expect(vm1.edit).toBe(false); + + vm1.$el.querySelector('.confidential-edit').click(); + + expect(vm1.edit).toBe(true); + + setTimeout(() => { + expect( + vm1.$el + .innerHTML + .includes('You are going to turn off the confidentiality.'), + ).toBe(true); + + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index ab8a3f6c64c..7ee998c8fce 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -1,7 +1,6 @@ import Vue from 'vue'; import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; -import { statusIconEntityMap } from '~/vue_shared/ci_status_icons'; const deploymentMockData = [ { @@ -43,15 +42,6 @@ describe('MRWidgetDeployment', () => { }); }); - describe('computed', () => { - describe('svg', () => { - it('should have the proper SVG icon', () => { - const vm = createComponent(deploymentMockData); - expect(vm.svg).toEqual(statusIconEntityMap.icon_status_success); - }); - }); - }); - describe('methods', () => { let vm = createComponent(); const deployment = deploymentMockData[0]; diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js index 6adcbc73ed7..2ae3adc1f93 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -52,10 +52,10 @@ const createComponent = () => { }; const messages = { - loadingMetrics: 'Loading deployment statistics.', + loadingMetrics: 'Loading deployment statistics', hasMetrics: 'Memory usage unchanged from 0MB to 0MB', - loadFailed: 'Failed to load deployment statistics.', - metricsUnavailable: 'Deployment statistics are not available currently.', + loadFailed: 'Failed to load deployment statistics', + metricsUnavailable: 'Deployment statistics are not available currently', }; describe('MemoryUsage', () => { diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index 647b59520f8..c763487d12f 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -81,13 +81,12 @@ describe('MRWidgetPipeline', () => { expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1); expect(el.querySelector('.pipeline-id').textContent).toContain(`#${pipeline.id}`); expect(el.innerText).toContain('passed'); - expect(el.innerText).toContain('with stages'); expect(el.querySelector('.pipeline-id').getAttribute('href')).toEqual(pipeline.path); expect(el.querySelectorAll('.stage-container').length).toEqual(2); expect(el.querySelector('.js-ci-error')).toEqual(null); expect(el.querySelector('.js-commit-link').getAttribute('href')).toEqual(pipeline.commit.commit_path); expect(el.querySelector('.js-commit-link').textContent).toContain(pipeline.commit.short_id); - expect(el.querySelector('.js-mr-coverage').textContent).toContain(`Coverage ${pipeline.coverage}%.`); + expect(el.querySelector('.js-mr-coverage').textContent).toContain(`Coverage ${pipeline.coverage}%`); }); it('should list single stage', (done) => { @@ -95,7 +94,6 @@ describe('MRWidgetPipeline', () => { Vue.nextTick(() => { expect(el.querySelectorAll('.stage-container button').length).toEqual(1); - expect(el.innerText).toContain('with stage'); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js index f6e0c3dfb74..f86fb6a0b4b 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js @@ -22,15 +22,16 @@ describe('MRWidgetRelatedLinks', () => { }); describe('computed', () => { + const data = { + relatedLinks: { + closing: '/foo', + mentioned: '/foo', + assignToMe: '/foo', + }, + }; + describe('hasLinks', () => { it('should return correct value when we have links reference', () => { - const data = { - relatedLinks: { - closing: '/foo', - mentioned: '/foo', - assignToMe: '/foo', - }, - }; const vm = createComponent(data); expect(vm.hasLinks).toBeTruthy(); @@ -44,44 +45,24 @@ describe('MRWidgetRelatedLinks', () => { expect(vm.hasLinks).toBeFalsy(); }); }); - }); - - describe('methods', () => { - const data = { - relatedLinks: { - closing: '<a href="#">#23</a> and <a>#42</a>', - mentioned: '<a href="#">#7</a>', - }, - }; - const vm = createComponent(data); - - describe('hasMultipleIssues', () => { - it('should return true if the given text has multiple issues', () => { - expect(vm.hasMultipleIssues(data.relatedLinks.closing)).toBeTruthy(); - }); - - it('should return false if the given text has one issue', () => { - expect(vm.hasMultipleIssues(data.relatedLinks.mentioned)).toBeFalsy(); - }); - }); - describe('issueLabel', () => { - it('should return true if the given text has multiple issues', () => { - expect(vm.issueLabel('closing')).toEqual('issues'); - }); - - it('should return false if the given text has one issue', () => { - expect(vm.issueLabel('mentioned')).toEqual('issue'); + describe('closesText', () => { + it('returns correct text for open merge request', () => { + data.state = 'open'; + const vm = createComponent(data); + expect(vm.closesText).toEqual('Closes'); }); - }); - describe('verbLabel', () => { - it('should return true if the given text has multiple issues', () => { - expect(vm.verbLabel('closing')).toEqual('are'); + it('returns correct text for closed merge request', () => { + data.state = 'closed'; + const vm = createComponent(data); + expect(vm.closesText).toEqual('Did not close'); }); - it('should return false if the given text has one issue', () => { - expect(vm.verbLabel('mentioned')).toEqual('is'); + it('returns correct tense for merged request', () => { + data.state = 'merged'; + const vm = createComponent(data); + expect(vm.closesText).toEqual('Closed'); }); }); }); @@ -95,8 +76,8 @@ describe('MRWidgetRelatedLinks', () => { }); const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - expect(content).toContain('Closes issues #23 and #42'); - expect(content).not.toContain('mentioned'); + expect(content).toContain('Closes #23 and #42'); + expect(content).not.toContain('Mentions'); }); it('should have only have mentioned issues text', () => { @@ -106,8 +87,7 @@ describe('MRWidgetRelatedLinks', () => { }, }); - expect(vm.$el.innerText).toContain('issue #7'); - expect(vm.$el.innerText).toContain('is mentioned but will not be closed.'); + expect(vm.$el.innerText).toContain('Mentions #7'); expect(vm.$el.innerText).not.toContain('Closes'); }); @@ -120,9 +100,8 @@ describe('MRWidgetRelatedLinks', () => { }); const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - expect(content).toContain('Closes issue #7.'); - expect(content).toContain('issues #23 and #42'); - expect(content).toContain('are mentioned but will not be closed.'); + expect(content).toContain('Closes #7'); + expect(content).toContain('Mentions #23 and #42'); }); it('should have assing issues link', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js index cac2f561a0b..4869fb17d96 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js @@ -12,7 +12,7 @@ describe('MRWidgetArchived', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy(); expect(el.querySelector('button').disabled).toBeTruthy(); - expect(el.innerText).toContain('This project is archived, write access has been disabled.'); + expect(el.innerText).toContain('This project is archived, write access has been disabled'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js index 47b4ba893e0..6042d7384d5 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js @@ -24,8 +24,8 @@ describe('MRWidgetAutoMergeFailed', () => { it('should have correct elements', () => { expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically.'); + expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeFalsy(); + expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically'); expect(vm.$el.innerText).toContain(mergeError); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js index 3be11d47227..6b7aa935ad3 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js @@ -12,7 +12,7 @@ describe('MRWidgetChecking', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy(); expect(el.querySelector('button').disabled).toBeTruthy(); - expect(el.innerText).toContain('Checking ability to merge automatically.'); + expect(el.innerText).toContain('Checking ability to merge automatically'); expect(el.querySelector('i')).toBeDefined(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index e7ae85caec4..3b7b7d93662 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -29,15 +29,16 @@ describe('MRWidgetConflicts', () => { describe('template', () => { it('should have correct elements', () => { const el = createComponent().$el; - const resolveButton = el.querySelectorAll('.btn-group .btn')[0]; - const mergeLocallyButton = el.querySelectorAll('.btn-group .btn')[1]; + const resolveButton = el.querySelector('.js-resolve-conflicts-button'); + const mergeButton = el.querySelector('.mr-widget-body .btn'); + const mergeLocallyButton = el.querySelector('.js-merge-locally-button'); - expect(el.textContent).toContain('There are merge conflicts.'); + expect(el.textContent).toContain('There are merge conflicts'); expect(el.textContent).not.toContain('ask someone with write access'); expect(el.querySelector('.btn-success').disabled).toBeTruthy(); - expect(el.querySelectorAll('.btn-group .btn').length).toBe(2); expect(resolveButton.textContent).toContain('Resolve conflicts'); expect(resolveButton.getAttribute('href')).toEqual(path); + expect(mergeButton.textContent).toContain('Merge'); expect(mergeLocallyButton.textContent).toContain('Merge locally'); }); @@ -59,8 +60,8 @@ describe('MRWidgetConflicts', () => { it('should not have action buttons', (done) => { Vue.nextTick(() => { expect(vm.$el.querySelectorAll('.btn').length).toBe(1); - expect(vm.$el.querySelector('a.js-resolve-conflicts-button')).toEqual(null); - expect(vm.$el.querySelector('a.js-merge-locally-button')).toEqual(null); + expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toEqual(null); + expect(vm.$el.querySelector('.js-merge-locally-button')).toEqual(null); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js index 587b83430d9..cef365eec8a 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js @@ -94,7 +94,7 @@ describe('MRWidgetFailedToMerge', () => { expect(el.querySelector('button').innerText).toContain('Merge'); expect(el.querySelector('.js-refresh-button').innerText).toContain('Refresh now'); expect(el.querySelector('.js-refresh-label')).toEqual(null); - expect(el.innerText).not.toContain('Refreshing now...'); + expect(el.innerText).not.toContain('Refreshing now'); setTimeout(() => { expect(el.innerText).toContain('Refreshing in 9 seconds'); done(); @@ -115,7 +115,7 @@ describe('MRWidgetFailedToMerge', () => { vm.refresh(); Vue.nextTick(() => { expect(el.innerText).not.toContain('Merge failed. Refreshing'); - expect(el.innerText).toContain('Refreshing now...'); + expect(el.innerText).toContain('Refreshing now'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js index fb2ef606604..237035648cf 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import lockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_locked'; +import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging'; -describe('MRWidgetLocked', () => { +describe('MRWidgetMerging', () => { describe('props', () => { it('should have props', () => { - const { mr } = lockedComponent.props; + const { mr } = mergingComponent.props; expect(mr.type instanceof Object).toBeTruthy(); expect(mr.required).toBeTruthy(); @@ -13,7 +13,7 @@ describe('MRWidgetLocked', () => { describe('template', () => { it('should have correct elements', () => { - const Component = Vue.extend(lockedComponent); + const Component = Vue.extend(mergingComponent); const mr = { targetBranchPath: '/branch-path', targetBranch: 'branch', @@ -24,7 +24,7 @@ describe('MRWidgetLocked', () => { }).$el; expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('it is locked'); + expect(el.innerText).toContain('This merge request is in the process of being merged'); expect(el.innerText).toContain('changes will be merged into'); expect(el.querySelector('.label-branch a').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.querySelector('.label-branch a').textContent).toContain(mr.targetBranch); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js index 8d8b90cea16..9a71d0b47d7 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js @@ -162,10 +162,10 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { it('should have correct elements', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('to be merged automatically when the pipeline succeeds.'); + expect(el.innerText).toContain('to be merged automatically when the pipeline succeeds'); expect(el.innerText).toContain('The changes will be merged into'); expect(el.innerText).toContain(targetBranch); - expect(el.innerText).toContain('The source branch will not be removed.'); + expect(el.innerText).toContain('The source branch will not be removed'); expect(el.querySelector('.js-cancel-auto-merge').innerText).toContain('Cancel automatic merge'); expect(el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy(); expect(el.querySelector('.js-remove-source-branch').innerText).toContain('Remove source branch'); @@ -186,8 +186,8 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { Vue.nextTick(() => { const normalizedText = el.innerText.replace(/\s+/g, ' '); - expect(normalizedText).toContain('The source branch will be removed.'); - expect(normalizedText).not.toContain('The source branch will not be removed.'); + expect(normalizedText).toContain('The source branch will be removed'); + expect(normalizedText).not.toContain('The source branch will not be removed'); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index 6628010112d..afaa750199a 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -142,19 +142,19 @@ describe('MRWidgetMerged', () => { expect(el.querySelector('.js-mr-widget-author')).toBeDefined(); expect(el.innerText).toContain('The changes were merged into'); expect(el.innerText).toContain(targetBranch); - expect(el.innerText).toContain('The source branch has been removed.'); + expect(el.innerText).toContain('The source branch has been removed'); expect(el.innerText).toContain('Revert'); expect(el.innerText).toContain('Cherry-pick'); - expect(el.innerText).not.toContain('You can remove source branch now.'); - expect(el.innerText).not.toContain('The source branch is being removed.'); + expect(el.innerText).not.toContain('You can remove source branch now'); + expect(el.innerText).not.toContain('The source branch is being removed'); }); it('should not show source branch removed text', (done) => { vm.mr.sourceBranchRemoved = false; Vue.nextTick(() => { - expect(el.innerText).toContain('You can remove source branch now.'); - expect(el.innerText).not.toContain('The source branch has been removed.'); + expect(el.innerText).toContain('You can remove source branch now'); + expect(el.innerText).not.toContain('The source branch has been removed'); done(); }); }); @@ -164,9 +164,9 @@ describe('MRWidgetMerged', () => { vm.mr.sourceBranchRemoved = false; Vue.nextTick(() => { - expect(el.innerText).toContain('The source branch is being removed.'); - expect(el.innerText).not.toContain('You can remove source branch now.'); - expect(el.innerText).not.toContain('The source branch has been removed.'); + expect(el.innerText).toContain('The source branch is being removed'); + expect(el.innerText).not.toContain('You can remove source branch now'); + expect(el.innerText).not.toContain('The source branch has been removed'); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js index 98674d12afb..720effb5c1c 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js @@ -49,7 +49,7 @@ describe('MRWidgetMissingBranch', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); expect(content).toContain('source branch does not exist.'); - expect(content).toContain('Please restore the source branch or use a different source branch.'); + expect(content).toContain('Please restore it or use a different source branch'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js index 61e00f4cf79..33f20ab132d 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js @@ -11,7 +11,7 @@ describe('MRWidgetNotAllowed', () => { expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); expect(vm.$el.innerText).toContain('Ready to be merged automatically.'); - expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request.'); + expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js index b293d118571..d0702f9f503 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js @@ -10,7 +10,7 @@ describe('MRWidgetPipelineBlocked', () => { it('should have correct elements', () => { expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed.'); + expect(vm.$el.innerText).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js index 807fba705d4..78bac1c61a5 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js @@ -10,7 +10,7 @@ describe('MRWidgetPipelineFailed', () => { it('should have correct elements', () => { expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure.'); + expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 732b516badd..c607c9746a4 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -72,7 +72,7 @@ describe('MRWidgetReadyToMerge', () => { const withDesc = 'Include description in commit message'; const withoutDesc = "Don't include description in commit message"; - it('should return message wit description', () => { + it('should return message with description', () => { expect(vm.commitMessageLinkTitle).toEqual(withDesc); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js index 5fb1d69a8b3..4c67504b642 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js @@ -10,7 +10,7 @@ describe('MRWidgetSHAMismatch', () => { it('should have correct elements', () => { expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging.'); + expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging'); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js index 45bd1a69964..2cb3aaa6951 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -78,7 +78,7 @@ describe('MRWidgetWIP', () => { it('should have correct elements', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('This merge request is currently Work In Progress and therefore unable to merge'); + expect(el.innerText).toContain('This is a Work in Progress'); expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); expect(el.querySelector('button').innerText).toContain('Merge'); expect(el.querySelector('.js-remove-wip').innerText).toContain('Resolve WIP status'); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index e6f96d5588b..0795d0aaa82 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -20,7 +20,6 @@ export default { "human_time_estimate": null, "human_total_time_spent": null, "in_progress_merge_commit_sha": null, - "locked_at": null, "merge_commit_sha": "53027d060246c8f47e4a9310fb332aa52f221775", "merge_error": null, "merge_params": { @@ -30,6 +29,7 @@ export default { "merge_user_id": null, "merge_when_pipeline_succeeds": false, "source_branch": "daaaa", + "source_branch_link": "daaaa", "source_project_id": 19, "target_branch": "master", "target_project_id": 19, diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 3a0c50b750f..669ee248bf1 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -342,7 +342,7 @@ describe('mrWidgetOptions', () => { expect(comps['mr-widget-related-links']).toBeDefined(); expect(comps['mr-widget-merged']).toBeDefined(); expect(comps['mr-widget-closed']).toBeDefined(); - expect(comps['mr-widget-locked']).toBeDefined(); + expect(comps['mr-widget-merging']).toBeDefined(); expect(comps['mr-widget-failed-to-merge']).toBeDefined(); expect(comps['mr-widget-wip']).toBeDefined(); expect(comps['mr-widget-archived']).toBeDefined(); diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 267318faed4..fb3ef04b860 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -32,7 +32,7 @@ describe API::Helpers::Pagination do context 'when resource can be paginated' do before do - create_list(:empty_project, 3) + create_list(:project, 3) end describe 'first page' do diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb index 27532f96f56..7c0ba9ee67f 100644 --- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Banzai::Filter::AbstractReferenceFilter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe '#references_per_project' do it 'returns a Hash containing references grouped per project paths' do - doc = Nokogiri::HTML.fragment("#1 #{project.path_with_namespace}#2") + doc = Nokogiri::HTML.fragment("#1 #{project.full_path}#2") filter = described_class.new(doc, project: project) expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue) @@ -14,7 +14,7 @@ describe Banzai::Filter::AbstractReferenceFilter do refs = filter.references_per_project expect(refs).to be_an_instance_of(Hash) - expect(refs[project.path_with_namespace]).to eq(Set.new(%w[1 2])) + expect(refs[project.full_path]).to eq(Set.new(%w[1 2])) end end @@ -24,10 +24,10 @@ describe Banzai::Filter::AbstractReferenceFilter do filter = described_class.new(doc, project: project) expect(filter).to receive(:references_per_project) - .and_return({ project.path_with_namespace => Set.new(%w[1]) }) + .and_return({ project.full_path => Set.new(%w[1]) }) expect(filter.projects_per_reference) - .to eq({ project.path_with_namespace => project }) + .to eq({ project.full_path => project }) end end @@ -37,7 +37,7 @@ describe Banzai::Filter::AbstractReferenceFilter do context 'with RequestStore disabled' do it 'returns a list of Projects for a list of paths' do - expect(filter.find_projects_for_paths([project.path_with_namespace])) + expect(filter.find_projects_for_paths([project.full_path])) .to eq([project]) end @@ -49,7 +49,7 @@ describe Banzai::Filter::AbstractReferenceFilter do context 'with RequestStore enabled', :request_store do it 'returns a list of Projects for a list of paths' do - expect(filter.find_projects_for_paths([project.path_with_namespace])) + expect(filter.find_projects_for_paths([project.full_path])) .to eq([project]) end @@ -88,7 +88,7 @@ describe Banzai::Filter::AbstractReferenceFilter do doc = Nokogiri::HTML.fragment('') filter = described_class.new(doc, project: project) - expect(filter.current_project_path).to eq(project.path_with_namespace) + expect(filter.current_project_path).to eq(project.full_path) end end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index 11d48387544..935146c17fc 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -100,7 +100,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do context 'cross-project / cross-namespace complete reference' do let(:project2) { create(:project, :public, :repository) } - let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } + let(:reference) { "#{project2.full_path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -113,20 +113,20 @@ describe Banzai::Filter::CommitRangeReferenceFilter do doc = reference_filter("Fixed (#{reference}.)") expect(doc.css('a').first.text) - .to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}") + .to eql("#{project2.full_path}@#{commit1.short_id}...#{commit2.short_id}") end it 'has valid text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.text).to eql("Fixed (#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}.)") + expect(doc.text).to eql("Fixed (#{project2.full_path}@#{commit1.short_id}...#{commit2.short_id}.)") end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id.reverse}...#{commit2.id}" + exp = act = "Fixed #{project2.full_path}@#{commit1.id.reverse}...#{commit2.id}" expect(reference_filter(act).to_html).to eq exp - exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id}...#{commit2.id.reverse}" + exp = act = "Fixed #{project2.full_path}@#{commit1.id}...#{commit2.id.reverse}" expect(reference_filter(act).to_html).to eq exp end end diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index fb2a36d1ba1..702fcac0c6f 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -98,18 +98,18 @@ describe Banzai::Filter::CommitReferenceFilter do let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + let(:reference) { "#{project2.full_path}@#{commit.short_id}" } it 'link has valid text' do doc = reference_filter("See (#{reference}.)") - expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}@#{commit.short_id}") + expect(doc.css('a').first.text).to eql("#{project2.full_path}@#{commit.short_id}") end it 'has valid text' do doc = reference_filter("See (#{reference}.)") - expect(doc.text).to eql("See (#{project2.path_with_namespace}@#{commit.short_id}.)") + expect(doc.text).to eql("See (#{project2.full_path}@#{commit.short_id}.)") end it 'ignores invalid commit IDs on the referenced project' do @@ -121,10 +121,10 @@ describe Banzai::Filter::CommitReferenceFilter do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + let(:reference) { "#{project2.full_path}@#{commit.short_id}" } it 'link has valid text' do doc = reference_filter("See (#{reference}.)") @@ -147,10 +147,10 @@ describe Banzai::Filter::CommitReferenceFilter do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + let(:reference) { "#{project2.full_path}@#{commit.short_id}" } it 'link has valid text' do doc = reference_filter("See (#{reference}.)") diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 663e3514436..97d612e6347 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::GollumTagsFilter do include FilterSpecHelper - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { double } let(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb index bc7cae1df8d..cacb33d3372 100644 --- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb @@ -7,8 +7,8 @@ describe Banzai::Filter::IssuableStateFilter do let(:user) { create(:user) } let(:context) { { current_user: user, issuable_state_filter_enabled: true } } let(:closed_issue) { create_issue(:closed) } - let(:project) { create(:empty_project, :public) } - let(:other_project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } + let(:other_project) { create(:project, :public) } def create_link(text, data) link_to(text, '', class: 'gfm has-tooltip', data: data) diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 045bf3e0cc9..9c74c9b8c99 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -7,7 +7,7 @@ describe Banzai::Filter::IssueReferenceFilter do IssuesHelper end - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } it 'requires project context' do @@ -125,9 +125,9 @@ describe Banzai::Filter::IssueReferenceFilter do context 'cross-project / cross-namespace complete reference' do it_behaves_like 'a reference containing an element node' - let(:project2) { create(:empty_project, :public) } + let(:project2) { create(:project, :public) } let(:issue) { create(:issue, project: project2) } - let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } + let(:reference) { "#{project2.full_path}##{issue.iid}" } it 'ignores valid references when cross-reference project uses external tracker' do expect_any_instance_of(described_class).to receive(:find_object) @@ -148,13 +148,13 @@ describe Banzai::Filter::IssueReferenceFilter do it 'link has valid text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}##{issue.iid}") + expect(doc.css('a').first.text).to eql("#{project2.full_path}##{issue.iid}") end it 'has valid text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.text).to eq("Fixed (#{project2.path_with_namespace}##{issue.iid}.)") + expect(doc.text).to eq("Fixed (#{project2.full_path}##{issue.iid}.)") end it 'ignores invalid issue IDs on the referenced project' do @@ -168,10 +168,10 @@ describe Banzai::Filter::IssueReferenceFilter do it_behaves_like 'a reference containing an element node' let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } + let(:reference) { "#{project2.full_path}##{issue.iid}" } it 'ignores valid references when cross-reference project uses external tracker' do expect_any_instance_of(described_class).to receive(:find_object) @@ -212,8 +212,8 @@ describe Banzai::Filter::IssueReferenceFilter do it_behaves_like 'a reference containing an element node' let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } let(:reference) { "#{project2.path}##{issue.iid}" } @@ -256,7 +256,7 @@ describe Banzai::Filter::IssueReferenceFilter do it_behaves_like 'a reference containing an element node' let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" } @@ -277,7 +277,7 @@ describe Banzai::Filter::IssueReferenceFilter do it_behaves_like 'a reference containing an element node' let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } let(:reference) { issue.to_reference(project) } let(:reference_link) { %{<a href="#{reference}">Reference</a>} } @@ -299,7 +299,7 @@ describe Banzai::Filter::IssueReferenceFilter do it_behaves_like 'a reference containing an element node' let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } let(:reference) { "#{helper.url_for_issue(issue.iid, project2) + "#note_123"}" } let(:reference_link) { %{<a href="#{reference}">Reference</a>} } @@ -324,10 +324,10 @@ describe Banzai::Filter::IssueReferenceFilter do filter = described_class.new(doc, project: project) expect(filter).to receive(:projects_per_reference) - .and_return({ project.path_with_namespace => project }) + .and_return({ project.full_path => project }) expect(filter).to receive(:references_per_project) - .and_return({ project.path_with_namespace => Set.new([issue.iid]) }) + .and_return({ project.full_path => Set.new([issue.iid]) }) expect(filter.issues_per_project) .to eq({ project => { issue.iid => issue } }) diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 1daa6ac7f9e..2cd30a5e302 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -4,7 +4,7 @@ require 'html/pipeline' describe Banzai::Filter::LabelReferenceFilter do include FilterSpecHelper - let(:project) { create(:empty_project, :public, name: 'sample-project') } + let(:project) { create(:project, :public, name: 'sample-project') } let(:label) { create(:label, project: project) } let(:reference) { label.to_reference } @@ -315,7 +315,7 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'group label references' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:group_label) { create(:group_label, name: 'gfm references', group: group) } context 'without project reference' do @@ -366,9 +366,9 @@ describe Banzai::Filter::LabelReferenceFilter do end describe 'cross-project / cross-namespace complete reference' do - let(:project2) { create(:empty_project) } + let(:project2) { create(:project) } let(:label) { create(:label, project: project2, color: '#00ff00') } - let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let(:reference) { "#{project2.full_path}~#{label.name}" } let!(:result) { reference_filter("See #{reference}") } it 'links to a valid reference' do @@ -397,10 +397,10 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } + let(:project2) { create(:project, namespace: namespace) } let(:label) { create(:label, project: project2, color: '#00ff00') } - let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let(:reference) { "#{project2.full_path}~#{label.name}" } let!(:result) { reference_filter("See #{reference}") } it 'links to a valid reference' do @@ -429,8 +429,8 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } + let(:project2) { create(:project, namespace: namespace) } let(:label) { create(:label, project: project2, color: '#00ff00') } let(:reference) { "#{project2.path}~#{label.name}" } let!(:result) { reference_filter("See #{reference}") } @@ -462,11 +462,11 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'cross group label references' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:another_group) { create(:group) } - let(:another_project) { create(:empty_project, :public, namespace: another_group) } + let(:another_project) { create(:project, :public, namespace: another_group) } let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } - let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let(:reference) { "#{another_project.full_path}~#{group_label.name}" } let!(:result) { reference_filter("See #{reference}", project: project) } it 'points to referenced project issues page' do @@ -498,10 +498,10 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'cross-project / same-group_label complete reference' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } - let(:another_project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } + let(:another_project) { create(:project, :public, namespace: group) } let(:group_label) { create(:group_label, group: group, color: '#00ff00') } - let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let(:reference) { "#{another_project.full_path}~#{group_label.name}" } let!(:result) { reference_filter("See #{reference}", project: project) } it 'points to referenced project issues page' do @@ -533,9 +533,9 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'same project / same group_label complete reference' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:group_label) { create(:group_label, group: group, color: '#00ff00') } - let(:reference) { "#{project.path_with_namespace}~#{group_label.name}" } + let(:reference) { "#{project.full_path}~#{group_label.name}" } let!(:result) { reference_filter("See #{reference}", project: project) } it 'points to referenced project issues page' do @@ -565,7 +565,7 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'same project / same group_label shorthand reference' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:group_label) { create(:group_label, group: group, color: '#00ff00') } let(:reference) { "#{project.path}~#{group_label.name}" } let!(:result) { reference_filter("See #{reference}", project: project) } diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 683972a3112..ed2788f8a33 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::MergeRequestReferenceFilter do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:merge) { create(:merge_request, source_project: project) } it 'requires project context' do @@ -100,9 +100,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do end context 'cross-project / cross-namespace complete reference' do - let(:project2) { create(:empty_project, :public) } + let(:project2) { create(:project, :public) } let(:merge) { create(:merge_request, source_project: project2) } - let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } + let(:reference) { "#{project2.full_path}!#{merge.iid}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -132,10 +132,10 @@ describe Banzai::Filter::MergeRequestReferenceFilter do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let!(:merge) { create(:merge_request, source_project: project2) } - let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } + let(:reference) { "#{project2.full_path}!#{merge.iid}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -165,8 +165,8 @@ describe Banzai::Filter::MergeRequestReferenceFilter do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let!(:merge) { create(:merge_request, source_project: project2) } let(:reference) { "#{project2.path}!#{merge.iid}" } @@ -198,7 +198,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' } diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 8fe05dc2e53..ebd6c79077e 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -3,57 +3,57 @@ require 'spec_helper' describe Banzai::Filter::MilestoneReferenceFilter do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } - let(:milestone) { create(:milestone, project: project) } - let(:reference) { milestone.to_reference } + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, group: group) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>milestone #{milestone.to_reference}</#{elem}>" - expect(reference_filter(act).to_html).to eq exp + shared_examples 'reference parsing' do + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>milestone #{reference}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end end - end - it 'includes default classes' do - doc = reference_filter("Milestone #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip' - end + it 'includes default classes' do + doc = reference_filter("Milestone #{reference}") - it 'includes a data-project attribute' do - doc = reference_filter("Milestone #{reference}") - link = doc.css('a').first + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip' + end - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end + it 'includes a data-project attribute' do + doc = reference_filter("Milestone #{reference}") + link = doc.css('a').first - it 'includes a data-milestone attribute' do - doc = reference_filter("See #{reference}") - link = doc.css('a').first + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end - expect(link).to have_attribute('data-milestone') - expect(link.attr('data-milestone')).to eq milestone.id.to_s - end + it 'includes a data-milestone attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first - it 'supports an :only_path context' do - doc = reference_filter("Milestone #{reference}", only_path: true) - link = doc.css('a').first.attr('href') + expect(link).to have_attribute('data-milestone') + expect(link.attr('data-milestone')).to eq milestone.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Milestone #{reference}", only_path: true) + link = doc.css('a').first.attr('href') - expect(link).not_to match %r(https?://) - expect(link).to eq urls - .project_milestone_path(project, milestone) + expect(link).not_to match %r(https?://) + expect(link).to eq urls.milestone_path(milestone) + end end - context 'Integer-based references' do + shared_examples 'Integer-based references' do it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) end it 'links with adjacent text' do @@ -68,15 +68,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - context 'String-based single-word references' do - let(:milestone) { create(:milestone, name: 'gfm', project: project) } + shared_examples 'String-based single-word references' do let(:reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + before do + milestone.update!(name: 'gfm') + end + it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) expect(doc.text).to eq 'See gfm' end @@ -92,15 +94,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - context 'String-based multi-word references in quotes' do - let(:milestone) { create(:milestone, name: 'gfm references', project: project) } + shared_examples 'String-based multi-word references in quotes' do let(:reference) { milestone.to_reference(format: :name) } + before do + milestone.update!(name: 'gfm references') + end + it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) expect(doc.text).to eq 'See gfm references' end @@ -116,23 +120,27 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'referencing a milestone in a link href' do - let(:reference) { %Q{<a href="#{milestone.to_reference}">Milestone</a>} } + shared_examples 'referencing a milestone in a link href' do + let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + let(:link_reference) { %Q{<a href="#{unquoted_reference}">Milestone</a>} } + + before do + milestone.update!(name: 'gfm') + end it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{link_reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) end it 'links with adjacent text' do - doc = reference_filter("Milestone (#{reference}.)") + doc = reference_filter("Milestone (#{link_reference}.)") expect(doc.to_html).to match(%r(\(<a.+>Milestone</a>\.\))) end it 'includes a data-project attribute' do - doc = reference_filter("Milestone #{reference}") + doc = reference_filter("Milestone #{link_reference}") link = doc.css('a').first expect(link).to have_attribute('data-project') @@ -140,7 +148,35 @@ describe Banzai::Filter::MilestoneReferenceFilter do end it 'includes a data-milestone attribute' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{link_reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-milestone') + expect(link.attr('data-milestone')).to eq milestone.id.to_s + end + end + + shared_examples 'linking to a milestone as the entire link' do + let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + let(:link) { urls.milestone_url(milestone) } + let(:link_reference) { %Q{<a href="#{link}">#{link}</a>} } + + it 'replaces the link text with the milestone reference' do + doc = reference_filter("See #{link}") + + expect(doc.css('a').first.text).to eq(unquoted_reference) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Milestone #{link_reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-milestone attribute' do + doc = reference_filter("See #{link_reference}") link = doc.css('a').first expect(link).to have_attribute('data-milestone') @@ -148,11 +184,11 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross-project / cross-namespace complete reference' do + shared_examples 'cross-project / cross-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:another_project) { create(:project, :public, namespace: namespace) } let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" } + let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do @@ -164,14 +200,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See (#{reference}.)") expect(doc.css('a').first.text) - .to eq("#{milestone.name} in #{another_project.path_with_namespace}") + .to eq("#{milestone.name} in #{another_project.full_path}") end it 'has valid text' do doc = reference_filter("See (#{reference}.)") expect(doc.text) - .to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)") + .to eq("See (#{milestone.name} in #{another_project.full_path}.)") end it 'escapes the name attribute' do @@ -180,16 +216,16 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.text) - .to eq "#{milestone.name} in #{another_project.path_with_namespace}" + .to eq "#{milestone.name} in #{another_project.full_path}" end end - describe 'cross-project / same-namespace complete reference' do + shared_examples 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:another_project) { create(:project, :public, namespace: namespace) } let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" } + let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do @@ -221,10 +257,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross project shorthand reference' do + shared_examples 'cross project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:another_project) { create(:project, :public, namespace: namespace) } let(:milestone) { create(:milestone, project: another_project) } let(:reference) { "#{another_project.path}%#{milestone.iid}" } let!(:result) { reference_filter("See #{reference}") } @@ -257,4 +293,54 @@ describe Banzai::Filter::MilestoneReferenceFilter do .to eq "#{milestone.name} in #{another_project.path}" end end + + context 'project milestones' do + let(:milestone) { create(:milestone, project: project) } + let(:reference) { milestone.to_reference } + + include_examples 'reference parsing' + + it_behaves_like 'Integer-based references' + it_behaves_like 'String-based single-word references' + it_behaves_like 'String-based multi-word references in quotes' + it_behaves_like 'referencing a milestone in a link href' + it_behaves_like 'cross-project / cross-namespace complete reference' + it_behaves_like 'cross-project / same-namespace complete reference' + it_behaves_like 'cross project shorthand reference' + end + + context 'group milestones' do + let(:milestone) { create(:milestone, group: group) } + let(:reference) { milestone.to_reference(format: :name) } + + include_examples 'reference parsing' + + it_behaves_like 'String-based single-word references' + it_behaves_like 'String-based multi-word references in quotes' + it_behaves_like 'referencing a milestone in a link href' + + it 'does not support references by IID' do + doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}") + + expect(doc.css('a')).to be_empty + end + + it 'does not support references by link' do + doc = reference_filter("See #{urls.milestone_url(milestone)}") + + expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone)) + end + + it 'does not support cross-project references' do + another_group = create(:group) + another_project = create(:project, :public, group: group) + project_reference = another_project.to_reference(project) + + milestone.update!(group: another_group) + + doc = reference_filter("See #{project_reference}#{reference}") + + expect(doc.css('a')).to be_empty + end + end end diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index fb6b81d4f10..68643effb66 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -17,7 +17,7 @@ describe Banzai::Filter::RedactorFilter do it 'skips when the skip_redaction flag is set' do user = create(:user) - project = create(:empty_project) + project = create(:project) link = reference_link(project: project.id, reference_type: 'test') doc = filter(link, current_user: user, skip_redaction: true) @@ -45,7 +45,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows permitted Project references' do user = create(:user) - project = create(:empty_project) + project = create(:project) project.team << [user, :master] link = reference_link(project: project.id, reference_type: 'test') @@ -62,7 +62,7 @@ describe Banzai::Filter::RedactorFilter do it 'removes unpermitted references' do user = create(:user) - project = create(:empty_project) + project = create(:project) link = reference_link(project: project.id, reference_type: 'test') doc = filter(link, current_user: user) @@ -82,7 +82,7 @@ describe Banzai::Filter::RedactorFilter do context 'for confidential issues' do it 'removes references for non project members' do non_member = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, :confidential, project: project) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') @@ -93,7 +93,7 @@ describe Banzai::Filter::RedactorFilter do it 'removes references for project members with guest role' do member = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) project.team << [member, :guest] issue = create(:issue, :confidential, project: project) @@ -105,7 +105,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for author' do author = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, :confidential, project: project, author: author) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') @@ -116,7 +116,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for assignee' do assignee = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, :confidential, project: project, assignees: [assignee]) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') @@ -127,7 +127,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for project members' do member = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) project.team << [member, :developer] issue = create(:issue, :confidential, project: project) @@ -139,7 +139,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for admin' do admin = create(:admin) - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, :confidential, project: project) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') @@ -151,7 +151,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for non confidential issues' do user = create(:user) - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, project: project) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb index b9ca68e8935..f96b6c83b0a 100644 --- a/spec/lib/banzai/filter/reference_filter_spec.rb +++ b/spec/lib/banzai/filter/reference_filter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Banzai::Filter::ReferenceFilter do - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } describe '#each_node' do it 'iterates over the nodes in a document' do diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index ddebf2264d9..08beede62db 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -26,7 +26,7 @@ describe Banzai::Filter::RelativeLinkFilter do end let(:project) { create(:project, :repository) } - let(:project_path) { project.path_with_namespace } + let(:project_path) { project.full_path } let(:ref) { 'markdown' } let(:commit) { project.commit(ref) } let(:project_wiki) { nil } @@ -56,7 +56,7 @@ describe Banzai::Filter::RelativeLinkFilter do end context 'without a repository' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } include_examples :preserve_unchanged end diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index 5f548888223..90ac4c7b238 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::SnippetReferenceFilter do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:snippet) { create(:project_snippet, project: project) } let(:reference) { snippet.to_reference } @@ -81,9 +81,9 @@ describe Banzai::Filter::SnippetReferenceFilter do context 'cross-project / cross-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let!(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" } + let(:reference) { "#{project2.full_path}$#{snippet.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -113,10 +113,10 @@ describe Banzai::Filter::SnippetReferenceFilter do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let!(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" } + let(:reference) { "#{project2.full_path}$#{snippet.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -146,8 +146,8 @@ describe Banzai::Filter::SnippetReferenceFilter do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:empty_project, :public, namespace: namespace) } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let!(:snippet) { create(:project_snippet, project: project2) } let(:reference) { "#{project2.path}$#{snippet.id}" } @@ -179,7 +179,7 @@ describe Banzai::Filter::SnippetReferenceFilter do context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } let(:snippet) { create(:project_snippet, project: project2) } let(:reference) { urls.project_snippet_url(project2, snippet) } diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb index 3bc9635b50e..60a88e903ef 100644 --- a/spec/lib/banzai/filter/upload_link_filter_spec.rb +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb @@ -29,7 +29,7 @@ describe Banzai::Filter::UploadLinkFilter do %(<div><a href="#{path}">#{path}</a></div>) end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } shared_examples :preserve_unchanged do it 'does not modify any relative URL in anchor' do @@ -52,21 +52,21 @@ describe Banzai::Filter::UploadLinkFilter do it 'rebuilds relative URL for a link' do doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) expect(doc.at_css('a')['href']) - .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" doc = filter(nested_link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) expect(doc.at_css('a')['href']) - .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" end it 'rebuilds relative URL for an image' do doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) expect(doc.at_css('img')['src']) - .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" doc = filter(nested_image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) expect(doc.at_css('img')['src']) - .to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" end it 'does not modify absolute URL' do @@ -85,7 +85,7 @@ describe Banzai::Filter::UploadLinkFilter do .to receive(:image?).with(path).and_return(true) doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png" + expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png" end end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 7ea9df5eda5..34dac1db69a 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::UserReferenceFilter do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:reference) { user.to_reference } diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb index ceafd12a68e..9596f004052 100644 --- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb @@ -4,7 +4,7 @@ describe Banzai::Filter::WikiLinkFilter do include FilterSpecHelper let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") } - let(:project) { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) } + let(:project) { build_stubbed(:project, :public, name: "wiki_link_project", namespace: namespace) } let(:user) { double } let(:wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb index 728271e757b..69763476dac 100644 --- a/spec/lib/banzai/issuable_extractor_spec.rb +++ b/spec/lib/banzai/issuable_extractor_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Banzai::IssuableExtractor do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:extractor) { described_class.new(project, user) } let(:issue) { create(:issue, project: project) } diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index dd2674f9f20..7f5d481c36c 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Banzai::ObjectRenderer do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { project.owner } let(:renderer) { described_class.new(project, user, custom_value: 'value') } let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) } @@ -28,7 +28,7 @@ describe Banzai::ObjectRenderer do it 'passes context to PostProcessPipeline' do another_user = create(:user) - another_project = create(:empty_project) + another_project = create(:project) object = Note.new( note: 'hello', note_html: 'hello', diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index 2501b638774..e9c7a2f352e 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe Banzai::Pipeline::FullPipeline do describe 'References' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } it 'handles markdown inside a reference' do diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 601ffbb5456..75413596431 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -36,7 +36,7 @@ describe Banzai::Pipeline::GfmPipeline do end it 'parses cross-project references to regular issues' do - other_project = create(:empty_project, :public) + other_project = create(:project, :public) issue = create(:issue, project: other_project) markdown = issue.to_reference(project, full: true) @@ -74,7 +74,7 @@ describe Banzai::Pipeline::GfmPipeline do end it 'parses cross-project references to regular issues' do - other_project = create(:empty_project, :public) + other_project = create(:project, :public) issue = create(:issue, project: other_project) markdown = issue.to_reference(project, full: true) diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index ac9bde6baf1..88ae4c1e07a 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -53,7 +53,7 @@ describe Banzai::Pipeline::WikiPipeline do describe "Links" do let(:namespace) { create(:namespace, name: "wiki_link_ns") } - let(:project) { create(:empty_project, :public, name: "wiki_link_project", namespace: namespace) } + let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) } let(:project_wiki) { ProjectWiki.new(project, double(:user)) } let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) } diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index 81ae5685b10..2424c3fdc66 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Banzai::Redactor do let(:user) { build(:user) } - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:redactor) { described_class.new(project, user) } describe '#redact' do diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index 0bf45329657..6175d4c4ca9 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -4,7 +4,7 @@ describe Banzai::ReferenceParser::BaseParser do include ReferenceParserHelpers let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } subject do klass = Class.new(described_class) do diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index 69bf28cdf85..3505659c2c3 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::CommitParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(project, user) } let(:link) { empty_html_link } diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb index b384a59bfb4..21813177deb 100644 --- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::CommitRangeParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(project, user) } let(:link) { empty_html_link } diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb index a3256afdbb1..25969b65168 100644 --- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::ExternalIssueParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(project, user) } let(:link) { empty_html_link } diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index 94b989fe91d..23dbe2b6238 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::IssueParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:issue) { create(:issue, project: project) } let(:link) { empty_html_link } diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb index cf1b2a92195..b700161d6c2 100644 --- a/spec/lib/banzai/reference_parser/label_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::LabelParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:label) { create(:label, project: project) } subject { described_class.new(project, user) } diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb index 2cfcafa8798..7dacdf8d629 100644 --- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::MilestoneParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } subject { described_class.new(project, user) } diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb index c6d0b7be254..69ec3f66aa8 100644 --- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::ReferenceParser::SnippetParser do include ReferenceParserHelpers - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:external_user) { create(:user, :external) } diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index 64f2b607d7c..e49726aca6c 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::UserParser do let(:group) { create(:group) } let(:user) { create(:user) } - let(:project) { create(:empty_project, :public, group: group, creator: user) } + let(:project) { create(:project, :public, group: group, creator: user) } subject { described_class.new(project, user) } let(:link) { empty_html_link } @@ -125,7 +125,7 @@ describe Banzai::ReferenceParser::UserParser do end it 'returns the nodes if the user can read the project' do - other_project = create(:empty_project, :public) + other_project = create(:project, :public) link['data-project'] = other_project.id.to_s @@ -137,7 +137,7 @@ describe Banzai::ReferenceParser::UserParser do end it 'returns an empty Array if the user can not read the project' do - other_project = create(:empty_project, :public) + other_project = create(:project, :public) link['data-project'] = other_project.id.to_s @@ -161,7 +161,7 @@ describe Banzai::ReferenceParser::UserParser do describe '#nodes_user_can_reference' do context 'when the link has a data-author attribute' do it 'returns the nodes when the user is a member of the project' do - other_project = create(:empty_project) + other_project = create(:project) other_project.team << [user, :developer] link['data-project'] = other_project.id.to_s @@ -178,7 +178,7 @@ describe Banzai::ReferenceParser::UserParser do end it 'returns an empty Array when the user could not be found' do - other_project = create(:empty_project) + other_project = create(:project) link['data-project'] = other_project.id.to_s link['data-author'] = '' @@ -187,7 +187,7 @@ describe Banzai::ReferenceParser::UserParser do end it 'returns an empty Array when the user is not a team member' do - other_project = create(:empty_project) + other_project = create(:project) link['data-project'] = other_project.id.to_s link['data-author'] = user.id.to_s diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb index 2c972da682e..bdf10a5e2a2 100644 --- a/spec/lib/bitbucket/paginator_spec.rb +++ b/spec/lib/bitbucket/paginator_spec.rb @@ -15,7 +15,7 @@ describe Bitbucket::Paginator do expect(paginator.items).to match(['item_2']) allow(paginator).to receive(:fetch_next_page).and_return(nil) - expect{ paginator.items }.to raise_error(StopIteration) + expect { paginator.items }.to raise_error(StopIteration) end end end diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb index 8e2d2724426..f0769deef21 100644 --- a/spec/lib/ci/charts_spec.rb +++ b/spec/lib/ci/charts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::Charts do context "pipeline_times" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:chart) { Ci::Charts::PipelineTime.new(project) } subject { chart.pipeline_times } diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ed571a2ba05..ee28387cd48 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1323,11 +1323,11 @@ EOT describe "Error handling" do it "fails to parse YAML" do - expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError) + expect {GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError) end it "indicates that object is invalid" do - expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError) + expect {GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError) end it "returns errors if tags parameter is invalid" do diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index e4b5dfc574a..92331eb2e5d 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectUrlConstrainer do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:namespace) { project.namespace } describe '#matches?' do diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb index 175fd2e7e13..c73faa55513 100644 --- a/spec/lib/container_registry/blob_spec.rb +++ b/spec/lib/container_registry/blob_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ContainerRegistry::Blob do let(:group) { create(:group, name: 'group') } - let(:project) { create(:empty_project, path: 'test', group: group) } + let(:project) { create(:project, path: 'test', group: group) } let(:repository) do create(:container_repository, name: 'image', diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb index c2bcb54210b..84cacdd3f0d 100644 --- a/spec/lib/container_registry/path_spec.rb +++ b/spec/lib/container_registry/path_spec.rb @@ -90,7 +90,7 @@ describe ContainerRegistry::Path do describe '#has_repository?' do context 'when project exists' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:path) { "#{project.full_path}/my/image" } context 'when path already has matching repository' do @@ -123,8 +123,8 @@ describe ContainerRegistry::Path do let(:path) { 'some_group/some_project' } before do - create(:empty_project, group: group, name: 'some_project') - create(:empty_project, name: 'some_project') + create(:project, group: group, name: 'some_project') + create(:project, name: 'some_project') end it 'returns a correct project' do @@ -142,7 +142,7 @@ describe ContainerRegistry::Path do context 'when matching multi-level path' do let(:project) do - create(:empty_project, group: group, name: 'some_project') + create(:project, group: group, name: 'some_project') end context 'when using the zero-level path' do @@ -192,7 +192,7 @@ describe ContainerRegistry::Path do let(:group) { create(:group, path: 'Some_Group') } before do - create(:empty_project, group: group, name: 'some_project') + create(:project, group: group, name: 'some_project') end context 'when project path equal repository path' do @@ -235,7 +235,7 @@ describe ContainerRegistry::Path do let(:group) { create(:group, path: 'SomeGroup') } before do - create(:empty_project, group: group, name: 'MyProject') + create(:project, group: group, name: 'MyProject') end it 'returns downcased project path' do diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index cb4ae3be525..e76463b5e7c 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ContainerRegistry::Tag do let(:group) { create(:group, name: 'group') } - let(:project) { create(:project, path: 'test', group: group) } + let(:project) { create(:project, :repository, path: 'test', group: group) } let(:repository) do create(:container_repository, name: '', project: project) diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb index b1366e74802..b0efcab47fb 100644 --- a/spec/lib/event_filter_spec.rb +++ b/spec/lib/event_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe EventFilter do describe '#apply_filter' do let(:source_user) { create(:user) } - let!(:public_project) { create(:empty_project, :public) } + let!(:public_project) { create(:project, :public) } let!(:push_event) { create(:event, :pushed, project: public_project, target: public_project, author: source_user) } let!(:merged_event) { create(:event, :merged, project: public_project, target: public_project, author: source_user) } diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 9b89f47ae7e..e13406d1972 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -14,7 +14,7 @@ describe ExtractsPath do repo = double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0', 'release/app', 'release/app/v1.0.0']) allow(project).to receive(:repository).and_return(repo) - allow(project).to receive(:path_with_namespace) + allow(project).to receive(:full_path) .and_return('gitlab/gitlab-ci') allow(request).to receive(:format=) end @@ -29,7 +29,7 @@ describe ExtractsPath do it "log tree path has no escape sequences" do assign_ref_vars - expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb") + expect(@logs_path).to eq("/#{@project.full_path}/refs/#{ref}/logs_tree/files/ruby/popen.rb") end context 'ref contains %20' do @@ -56,7 +56,7 @@ describe ExtractsPath do context 'subclass overrides get_id' do it 'uses ref returned by get_id' do - allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + allow_any_instance_of(self.class).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } assign_ref_vars diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb index 87733d53e92..9d80d480b52 100644 --- a/spec/lib/gitlab/allowable_spec.rb +++ b/spec/lib/gitlab/allowable_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::Allowable do let(:user) { create(:user) } context 'when user is allowed to do something' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } it 'reports correct ability to perform action' do expect(subject.can?(user, :read_project, project)).to be true @@ -17,7 +17,7 @@ describe Gitlab::Allowable do end context 'when user is not allowed to do something' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it 'reports correct ability to perform action' do expect(subject.can?(user, :read_project, project)).to be false diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index a9db0d5164d..4a498e79c87 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::Auth do end it 'recognizes other ci services' do - project = create(:empty_project) + project = create(:project) project.create_drone_ci_service(active: true) project.drone_ci_service.update(token: 'token') @@ -313,7 +313,8 @@ describe Gitlab::Auth do def full_authentication_abilities read_authentication_abilities + [ :push_code, - :create_container_image + :create_container_image, + :admin_container_image ] end end diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb new file mode 100644 index 00000000000..f4dfa53f050 --- /dev/null +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do + describe '#perform' do + set(:merge_request) { create(:merge_request) } + set(:merge_request_diff) { merge_request.merge_request_diff } + let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } + + def diffs_to_hashes(diffs) + diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) + end + + def quote_yaml(value) + MergeRequestDiff.connection.quote(YAML.dump(value)) + end + + def convert_to_yaml(merge_request_diff_id, commits, diffs) + MergeRequestDiff.where(id: merge_request_diff_id).update_all( + "st_commits = #{quote_yaml(commits)}, st_diffs = #{quote_yaml(diffs)}" + ) + end + + shared_examples 'updated MR diff' do + before do + convert_to_yaml(merge_request_diff.id, commits, diffs) + + MergeRequestDiffCommit.delete_all + MergeRequestDiffFile.delete_all + + subject.perform(merge_request_diff.id, merge_request_diff.id) + end + + it 'creates correct entries in the merge_request_diff_commits table' do + expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(commits.count) + expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(commits) + end + + it 'creates correct entries in the merge_request_diff_files table' do + expect(updated_merge_request_diff.merge_request_diff_files.count).to eq(expected_diffs.count) + expect(diffs_to_hashes(updated_merge_request_diff.raw_diffs)).to eq(expected_diffs) + end + + it 'sets the st_commits and st_diffs columns to nil' do + expect(updated_merge_request_diff.st_commits_before_type_cast).to be_nil + expect(updated_merge_request_diff.st_diffs_before_type_cast).to be_nil + end + end + + context 'when the diff IDs passed do not exist' do + it 'does not raise' do + expect { subject.perform(0, 0) }.not_to raise_exception + end + end + + context 'when the merge request diff has no serialised commits or diffs' do + before do + merge_request_diff.update(st_commits: nil, st_diffs: nil) + end + + it 'does not raise' do + expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } + .not_to raise_exception + end + end + + context 'processing multiple merge request diffs' do + let(:start_id) { described_class::MergeRequestDiff.minimum(:id) } + let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) } + + before do + merge_request.reload_diff(true) + + convert_to_yaml(start_id, merge_request_diff.commits, merge_request_diff.diffs) + convert_to_yaml(stop_id, updated_merge_request_diff.commits, updated_merge_request_diff.diffs) + + MergeRequestDiffCommit.delete_all + MergeRequestDiffFile.delete_all + end + + context 'when BUFFER_ROWS is exceeded' do + before do + stub_const("#{described_class}::BUFFER_ROWS", 1) + end + + it 'updates and continues' do + expect(described_class::MergeRequestDiff).to receive(:transaction).twice + + subject.perform(start_id, stop_id) + end + end + + context 'when BUFFER_ROWS is not exceeded' do + it 'only updates once' do + expect(described_class::MergeRequestDiff).to receive(:transaction).once + + subject.perform(start_id, stop_id) + end + end + end + + context 'when the merge request diff update fails' do + before do + allow(described_class::MergeRequestDiff) + .to receive(:update_all).and_raise(ActiveRecord::Rollback) + end + + it 'does not add any diff commits' do + expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } + .not_to change { MergeRequestDiffCommit.count } + end + + it 'does not add any diff files' do + expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } + .not_to change { MergeRequestDiffFile.count } + end + end + + context 'when the merge request diff has valid commits and diffs' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } + let(:expected_diffs) { diffs } + + include_examples 'updated MR diff' + end + + context 'when the merge request diffs have binary content' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_diffs) { diffs } + + # The start of a PDF created by Illustrator + let(:binary_string) do + "\x25\x50\x44\x46\x2d\x31\x2e\x35\x0d\x25\xe2\xe3\xcf\xd3\x0d\x0a".force_encoding(Encoding::BINARY) + end + + let(:diffs) do + [ + { + 'diff' => binary_string, + 'new_path' => 'path', + 'old_path' => 'path', + 'a_mode' => '100644', + 'b_mode' => '100644', + 'new_file' => false, + 'renamed_file' => false, + 'deleted_file' => false, + 'too_large' => false + } + ] + end + + include_examples 'updated MR diff' + end + + context 'when the merge request diff has commits, but no diffs' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:diffs) { [] } + let(:expected_diffs) { diffs } + + include_examples 'updated MR diff' + end + + context 'when the merge request diffs have invalid content' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:diffs) { ['--broken-diff'] } + let(:expected_diffs) { [] } + + include_examples 'updated MR diff' + end + + context 'when the merge request diffs are Rugged::Patch instances' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:diffs) { first_commit.rugged_diff_from_parent.patches } + let(:expected_diffs) { [] } + + include_examples 'updated MR diff' + end + + context 'when the merge request diffs are Rugged::Diff::Delta instances' do + let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:diffs) { first_commit.rugged_diff_from_parent.deltas } + let(:expected_diffs) { [] } + + include_examples 'updated MR diff' + end + end +end diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb index a910fb105a5..59f69d1e4b1 100644 --- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do describe '#perform' do it 'renames the path of system-uploads', truncate: true do - upload = create(:upload, model: create(:empty_project), path: 'uploads/system/project/avatar.jpg') + upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg') migration.perform('uploads/system/', 'uploads/-/system/') diff --git a/spec/lib/gitlab/backup/repository_spec.rb b/spec/lib/gitlab/backup/repository_spec.rb index 3af69daa585..535cce12780 100644 --- a/spec/lib/gitlab/backup/repository_spec.rb +++ b/spec/lib/gitlab/backup/repository_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Backup::Repository do let(:progress) { StringIO.new } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } before do allow(progress).to receive(:puts) diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb index 5e93935ea37..74eaf7eaf8b 100644 --- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb +++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb @@ -3,7 +3,7 @@ require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Coverage::Metadata do let(:badge) do - double(project: create(:empty_project), ref: 'feature', job: 'test') + double(project: create(:project), ref: 'feature', job: 'test') end let(:metadata) { described_class.new(badge) } diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb index 1547bd3228c..da789bf3705 100644 --- a/spec/lib/gitlab/badge/coverage/report_spec.rb +++ b/spec/lib/gitlab/badge/coverage/report_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Badge::Coverage::Report do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:job_name) { nil } let(:badge) do diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb index d537ce8803c..9032a8e9016 100644 --- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Pipeline::Metadata do - let(:badge) { double(project: create(:empty_project), ref: 'feature') } + let(:badge) { double(project: create(:project), ref: 'feature') } let(:metadata) { described_class.new(badge) } it_behaves_like 'badge metadata' diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index df66a031fec..a66347ead76 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -52,9 +52,9 @@ describe Gitlab::BitbucketImport::Importer do let(:project) do create( - :empty_project, + :project, import_source: project_identifier, - import_data: ProjectImportData.new(credentials: data) + import_data_attributes: { credentials: data } ) end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index d7f7b26740d..ae5b31dc12d 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do has_wiki?: false) end - let(:namespace){ create(:group, owner: user) } + let(:namespace) { create(:group, owner: user) } let(:token) { "asdasd12345" } let(:secret) { "sekrettt" } let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } } diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb index 0daf41a7c86..16704ff5e77 100644 --- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb +++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:pipeline_status) { described_class.new(project) } let(:cache_key) { "projects/#{project.id}/pipeline_status" } @@ -18,7 +18,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' } let(:ref) { 'master' } let(:pipeline_info) { { sha: sha, status: status, ref: ref } } - let!(:project_without_status) { create(:project) } + let!(:project_without_status) { create(:project, :repository) } describe '.load_in_batch_for_projects' do it 'preloads pipeline_status on projects' do @@ -48,8 +48,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do described_class.load_in_batch_for_projects([project_without_status]) end - it 'only connects to redis_cache twice' do - # Once to load, once to store in the cache + it 'only connects to redis twice' do + # Stub circuitbreaker so it doesn't count the redis connections in there + stub_circuit_breaker(project_without_status) expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original described_class.load_in_batch_for_projects([project_without_status]) @@ -195,7 +196,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do end it "doesn't fail for an empty project" do - status_for_empty_commit = described_class.new(create(:empty_project)) + status_for_empty_commit = described_class.new(create(:project)) status_for_empty_commit.load_status @@ -243,7 +244,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do end it "deletes the cache if the repository doesn't have a head commit" do - empty_project = create(:empty_project) + empty_project = create(:project) Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: 'sha', status: 'pending', ref: 'master' }) @@ -301,4 +302,13 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do end end end + + def stub_circuit_breaker(project) + fake_circuitbreaker = double + allow(fake_circuitbreaker).to receive(:perform).and_yield + allow(project.repository.raw_repository) + .to receive(:circuit_breaker).and_return(fake_circuitbreaker) + allow(project.repository) + .to receive(:circuit_breaker).and_return(fake_circuitbreaker) + end end diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index f5fd31e8d03..4a5b45e7cae 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Common do let(:user) { create(:user) } - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:pipeline) { create(:ci_pipeline, project: project) } subject do diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index 8814a7614a0..f5f03ac0395 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Common do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:stage) do diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index bbb40e2c1ab..432b07e4902 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Factory do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:stage) do diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 8ff6125ada1..15012495247 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor do - let(:project) { create(:empty_project) } - let(:project2) { create(:empty_project) } + let(:project) { create(:project) } + let(:project2) { create(:project) } let(:forked_project) { Projects::ForkService.new(project, project.creator).execute } let(:issue) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project2) } diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 79632e2b6a3..f1655854486 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -5,29 +5,31 @@ describe Gitlab::ContributionsCalendar do let(:user) { create(:user) } let(:private_project) do - create(:empty_project, :private) do |project| + create(:project, :private) do |project| create(:project_member, user: contributor, project: project) end end let(:public_project) do - create(:empty_project, :public) do |project| + create(:project, :public) do |project| create(:project_member, user: contributor, project: project) end end let(:feature_project) do - create(:empty_project, :public, :issues_private) do |project| + create(:project, :public, :issues_private) do |project| create(:project_member, user: contributor, project: project).project end end - let(:today) { Time.now.to_date } + let(:today) { Time.now.utc.to_date } + let(:yesterday) { today - 1.day } + let(:tomorrow) { today + 1.day } let(:last_week) { today - 7.days } let(:last_year) { today - 1.year } before do - travel_to today + travel_to Time.now.utc.end_of_day end after do @@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do described_class.new(contributor, current_user) end - def create_event(project, day) + def create_event(project, day, hour = 0) @targets ||= {} @targets[project] ||= create(:issue, project: project, author: contributor) @@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do action: Event::CREATED, target: @targets[project], author: contributor, - created_at: day + created_at: DateTime.new(day.year, day.month, day.day, hour) ) end @@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do expect(calendar(user).activity_dates[today]).to eq(0) expect(calendar(contributor).activity_dates[today]).to eq(2) end + + context "when events fall under different dates depending on the time zone" do + before do + create_event(public_project, today, 1) + create_event(public_project, today, 4) + create_event(public_project, today, 10) + create_event(public_project, today, 16) + create_event(public_project, today, 23) + end + + it "renders correct event counts within the UTC timezone" do + Time.use_zone('UTC') do + expect(calendar.activity_dates).to eq(today => 5) + end + end + + it "renders correct event counts within the Sydney timezone" do + Time.use_zone('Sydney') do + expect(calendar.activity_dates).to eq(today => 3, tomorrow => 2) + end + end + + it "renders correct event counts within the US Central timezone" do + Time.use_zone('Central Time (US & Canada)') do + expect(calendar.activity_dates).to eq(yesterday => 2, today => 3) + end + end + end end describe '#events_by_date' do diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb index d8757c601ab..854aaa34c73 100644 --- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::BaseEventFetcher do let(:max_events) { 2 } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user, :admin) } let(:start_time_attrs) { Issue.arel_table[:created_at] } let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] } diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index a1b3fe8509e..28ea7d4c303 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'cycle analytics events' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb index 2d85e712db0..2a0dd7be439 100644 --- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::Permissions do - let(:project) { create(:empty_project, public_builds: false) } + let(:project) { create(:project, public_builds: false) } let(:user) { create(:user) } subject { described_class.get(user: user, project: project) } diff --git a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb index 9c5e57342e9..c22d27f60d6 100644 --- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' shared_examples 'default query config' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) } it 'has the stage attribute' do diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 592448aef96..2e67c1c7f78 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::CycleAnalytics::StageSummary do end it "doesn't find issues from other projects" do - Timecop.freeze(5.days.from_now) { create(:issue, project: create(:empty_project)) } + Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) } expect(subject.first[:value]).to eq(0) end diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb new file mode 100644 index 00000000000..c519984a267 --- /dev/null +++ b/spec/lib/gitlab/daemon_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Gitlab::Daemon do + subject { described_class.new } + + before do + allow(subject).to receive(:start_working) + allow(subject).to receive(:stop_working) + end + + describe '.instance' do + before do + allow(Kernel).to receive(:at_exit) + end + + after(:each) do + described_class.instance_variable_set(:@instance, nil) + end + + it 'provides instance of Daemon' do + expect(described_class.instance).to be_instance_of(described_class) + end + + it 'subsequent invocations provide the same instance' do + expect(described_class.instance).to eq(described_class.instance) + end + + it 'creates at_exit hook when instance is created' do + expect(described_class.instance).not_to be_nil + + expect(Kernel).to have_received(:at_exit) + end + end + + describe 'when Daemon is enabled' do + before do + allow(subject).to receive(:enabled?).and_return(true) + end + + describe 'when Daemon is stopped' do + describe '#start' do + it 'starts the Daemon' do + expect { subject.start.join }.to change { subject.thread? }.from(false).to(true) + + expect(subject).to have_received(:start_working) + end + end + + describe '#stop' do + it "doesn't shutdown stopped Daemon" do + expect { subject.stop }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:start_working) + end + end + end + + describe 'when Daemon is running' do + before do + subject.start.join + end + + describe '#start' do + it "doesn't start running Daemon" do + expect { subject.start.join }.not_to change { subject.thread? } + + expect(subject).to have_received(:start_working).once + end + end + + describe '#stop' do + it 'shutdowns Daemon' do + expect { subject.stop }.to change { subject.thread? }.from(true).to(false) + + expect(subject).to have_received(:stop_working) + end + end + end + end + + describe 'when Daemon is disabled' do + before do + allow(subject).to receive(:enabled?).and_return(false) + end + + describe '#start' do + it "doesn't start working" do + expect(subject.start).to be_nil + expect { subject.start }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:start_working) + end + end + + describe '#stop' do + it "doesn't stop working" do + expect { subject.stop }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:stop_working) + end + end + end +end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index d3dbd82e8ba..ec2274a70aa 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -276,7 +276,7 @@ describe Gitlab::Database::MigrationHelpers do before do expect(model).to receive(:transaction_open?).and_return(false) - create_list(:empty_project, 5) + create_list(:project, 5) end it 'updates all the rows in a table' do diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index df7d1b5d27a..90aa4f63dd5 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -29,7 +29,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end describe '#remove_cached_html_for_projects' do - let(:project) { create(:empty_project, description_html: 'Project description') } + let(:project) { create(:project, description_html: 'Project description') } it 'removes description_html from projects' do subject.remove_cached_html_for_projects([project.id]) @@ -94,7 +94,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end it "renames the route for projects of the namespace" do - project = create(:project, path: "project-path", namespace: namespace) + project = create(:project, :repository, path: "project-path", namespace: namespace) subject.rename_path_for_routable(migration_namespace(namespace)) @@ -110,7 +110,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca it "doesn't rename routes that start with a similar name" do other_namespace = create(:namespace, path: 'the-path-but-not-really') - project = create(:empty_project, path: 'the-project', namespace: other_namespace) + project = create(:project, path: 'the-project', namespace: other_namespace) subject.rename_path_for_routable(migration_namespace(namespace)) @@ -120,7 +120,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca context "the-path namespace -> subgroup -> the-path0 project" do it "updates the route of the project correctly" do subgroup = create(:group, path: "subgroup", parent: namespace) - project = create(:project, path: "the-path0", namespace: subgroup) + project = create(:project, :repository, path: "the-path0", namespace: subgroup) subject.rename_path_for_routable(migration_namespace(namespace)) @@ -131,7 +131,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca context 'for projects' do let(:parent) { create(:namespace, path: 'the-parent') } - let(:project) { create(:empty_project, path: 'the-path', namespace: parent) } + let(:project) { create(:project, path: 'the-path', namespace: parent) } it 'renames the project called `the-path`' do subject.rename_path_for_routable(migration_project(project)) @@ -165,7 +165,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca it 'renames all the routes for the namespace' do child = create(:group, path: 'child', parent: namespace) - project = create(:project, namespace: child, path: 'the-project') + project = create(:project, :repository, namespace: child, path: 'the-project') other_one = create(:namespace, path: 'the-path-is-similar') subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 803e923b4a5..32ac0b88a9b 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -94,7 +94,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#move_repositories' do let(:namespace) { create(:group, name: 'hello-group') } it 'moves a project for a namespace' do - create(:project, namespace: namespace, path: 'hello-project') + create(:project, :repository, namespace: namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git') subject.move_repositories(namespace, 'hello-group', 'bye-group') @@ -104,7 +104,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : it 'moves a namespace in a subdirectory correctly' do child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, namespace: child_namespace, path: 'hello-project') + create(:project, :repository, namespace: child_namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git') @@ -115,7 +115,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : it 'moves a parent namespace with subdirectories' do child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, namespace: child_namespace, path: 'hello-project') + create(:project, :repository, namespace: child_namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git') subject.move_repositories(child_namespace, 'hello-group', 'renamed-group') @@ -166,7 +166,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#rename_namespace_dependencies' do it "moves the the repository for a project in the namespace" do - create(:project, namespace: namespace, path: "the-path-project") + create(:project, :repository, namespace: namespace, path: "the-path-project") expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git") subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') @@ -187,7 +187,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end it 'invalidates the markdown cache of related projects' do - project = create(:empty_project, namespace: namespace, path: "the-path-project") + project = create(:project, namespace: namespace, path: "the-path-project") expect(subject).to receive(:remove_cached_html_for_projects).with([project.id]) @@ -243,7 +243,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#revert_renames', redis: true do it 'renames the routes back to the previous values' do - project = create(:project, path: 'a-project', namespace: namespace) + project = create(:project, :repository, path: 'a-project', namespace: namespace) subject.rename_namespace(namespace) expect(subject).to receive(:perform_rename) @@ -261,7 +261,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end it 'moves the repositories back to their original place' do - project = create(:project, path: 'a-project', namespace: namespace) + project = create(:project, :repository, path: 'a-project', namespace: namespace) project.create_repository subject.rename_namespace(namespace) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index 0e240a5ccf1..595e06a9748 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } let(:project) do - create(:empty_project, + create(:project, path: 'the-path', namespace: create(:namespace, path: 'known-parent' )) end @@ -17,7 +17,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr describe '#projects_for_paths' do it 'searches using nested paths' do namespace = create(:namespace, path: 'hello') - project = create(:empty_project, path: 'THE-path', namespace: namespace) + project = create(:project, path: 'THE-path', namespace: namespace) result_ids = described_class.new(['Hello/the-path'], migration) .projects_for_paths.map(&:id) @@ -26,8 +26,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr end it 'includes the correct projects' do - project = create(:empty_project, path: 'THE-path') - _other_project = create(:empty_project) + project = create(:project, path: 'THE-path') + _other_project = create(:project) result_ids = subject.projects_for_paths.map(&:id) @@ -36,7 +36,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr end describe '#rename_projects' do - let!(:projects) { create_list(:empty_project, 2, path: 'the-path') } + let!(:projects) { create_list(:project, 2, path: 'the-path') } it 'renames each project' do expect(subject).to receive(:rename_project).twice @@ -104,7 +104,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr describe '#move_repository' do let(:known_parent) { create(:namespace, path: 'known-parent') } - let(:project) { create(:project, path: 'the-path', namespace: known_parent) } + let(:project) { create(:project, :repository, path: 'the-path', namespace: known_parent) } it 'moves the repository for a project' do expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git') diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index cd2fa98b14c..d3d841b0668 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -47,14 +47,6 @@ describe Gitlab::Diff::File do end end - describe '#old_content_commit' do - it 'returns base commit' do - old_content_commit = diff_file.old_content_commit - - expect(old_content_commit.id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') - end - end - describe '#old_blob' do it 'returns blob of commit of base commit' do old_data = diff_file.old_blob.data diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index c71568e2a65..8af49ed50ff 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::Parser do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.raw_diffs.first } let(:parser) { described_class.new } diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 0127b012c91..d0fa16ce4d1 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -36,15 +36,6 @@ describe Gitlab::Email::Handler::CreateNoteHandler do end end - context "when the email was auto generated" do - let!(:mail_key) { '636ca428858779856c226bb145ef4fad' } - let!(:email_raw) { fixture_file("emails/auto_reply.eml") } - - it "raises an AutoGeneratedEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError) - end - end - context "when the noteable could not be found" do before do noteable.destroy diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb index 66c38498e4e..21796694f26 100644 --- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do end let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:noteable) { create(:issue, project: project) } diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 7b3291b8315..83c4d177cae 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -117,7 +117,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#subject' do subject { message.subject } - it { is_expected.to include "[Git][#{project.path_with_namespace}]" } + it { is_expected.to include "[Git][#{project.full_path}]" } it { is_expected.to include "#{compare.commits.length} commits" } it { is_expected.to include compare.commits.first.message.split("\n").first } end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 88565ea5311..59f43abf26d 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -28,14 +28,6 @@ describe Gitlab::Email::Receiver do it "raises an UnknownIncomingEmail error" do expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail) end - - context "and the email contains no references header" do - let(:email_raw) { fixture_file("emails/auto_reply.eml").gsub(mail_key, "!!!") } - - it "raises an UnknownIncomingEmail error" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail) - end - end end context "when the email is blank" do @@ -45,4 +37,12 @@ describe Gitlab::Email::Receiver do expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) end end + + context "when the email was auto generated" do + let(:email_raw) { fixture_file("emails/auto_reply.eml") } + + it "raises an AutoGeneratedEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError) + end + end end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 1482ef7132d..8b14b227e65 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -30,6 +30,53 @@ describe Gitlab::EncodingHelper do it 'leaves binary string as is' do expect(ext_class.encode!(binary_string)).to eq(binary_string) end + + context 'with corrupted diff' do + let(:corrupted_diff) do + with_empty_bare_repository do |repo| + content = File.read(Rails.root.join( + 'spec/fixtures/encoding/Japanese.md').to_s) + commit_a = commit(repo, 'Japanese.md', content) + commit_b = commit(repo, 'Japanese.md', + content.sub('[TODO: Link]', '[現在作業中です: Link]')) + + repo.diff(commit_a, commit_b).each_line.map(&:content).join + end + end + + let(:cleaned_diff) do + corrupted_diff.dup.force_encoding('UTF-8') + .encode!('UTF-8', invalid: :replace, replace: '') + end + + let(:encoded_diff) do + described_class.encode!(corrupted_diff.dup) + end + + it 'does not corrupt data but remove invalid characters' do + expect(encoded_diff).to eq(cleaned_diff) + end + + def commit(repo, path, content) + oid = repo.write(content, :blob) + index = repo.index + + index.read_tree(repo.head.target.tree) unless repo.empty? + + index.add(path: path, oid: oid, mode: 0100644) + user = { name: 'Test', email: 'test@example.com' } + + Rugged::Commit.create( + repo, + tree: index.write_tree(repo), + author: user, + committer: user, + message: "Update #{path}", + parents: repo.empty? ? [] : [repo.head.target].compact, + update_ref: 'HEAD' + ) + end + end end describe '#encode_utf8' do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index eaec699ad90..a3d323fe28a 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } - let(:old_project) { create(:empty_project, name: 'old-project') } - let(:new_project) { create(:empty_project, name: 'new-project') } + let(:old_project) { create(:project, name: 'old-project') } + let(:new_project) { create(:project, name: 'new-project') } let(:user) { create(:user) } before do diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index c3016f63ebf..39e3b875c49 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::UploadsRewriter do let(:user) { create(:user) } - let(:old_project) { create(:empty_project) } - let(:new_project) { create(:empty_project) } + let(:old_project) { create(:project) } + let(:new_project) { create(:project) } let(:rewriter) { described_class.new(text, old_project, user) } context 'text contains links to uploads' do @@ -39,8 +39,8 @@ describe Gitlab::Gfm::UploadsRewriter do it 'copies files' do expect(new_files).to all(exist) expect(old_paths).not_to match_array new_paths - expect(old_paths).to all(include(old_project.path_with_namespace)) - expect(new_paths).to all(include(new_project.path_with_namespace)) + expect(old_paths).to all(include(old_project.full_path)) + expect(new_paths).to all(include(new_project.full_path)) end it 'does not remove old files' do diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 66c016d14b3..800c245b130 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -7,63 +7,73 @@ describe Gitlab::Git::Blame, seed_helper: true do Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") end - context "each count" do - it do - data = [] - blame.each do |commit, line| - data << { - commit: commit, - line: line - } - end - - expect(data.size).to eq(95) - expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) - expect(data.first[:line]).to eq("# Contribute to GitLab") - expect(data.first[:line]).to be_utf8 - end - end + shared_examples 'blaming a file' do + context "each count" do + it do + data = [] + blame.each do |commit, line| + data << { + commit: commit, + line: line + } + end - context "ISO-8859 encoding" do - let(:blame) do - Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") + expect(data.size).to eq(95) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq("# Contribute to GitLab") + expect(data.first[:line]).to be_utf8 + end end - it 'converts to UTF-8' do - data = [] - blame.each do |commit, line| - data << { - commit: commit, - line: line - } + context "ISO-8859 encoding" do + let(:blame) do + Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") end - expect(data.size).to eq(1) - expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) - expect(data.first[:line]).to eq("Ä ü") - expect(data.first[:line]).to be_utf8 - end - end + it 'converts to UTF-8' do + data = [] + blame.each do |commit, line| + data << { + commit: commit, + line: line + } + end - context "unknown encoding" do - let(:blame) do - Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") + expect(data.size).to eq(1) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq("Ä ü") + expect(data.first[:line]).to be_utf8 + end end - it 'converts to UTF-8' do - expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil) - data = [] - blame.each do |commit, line| - data << { + context "unknown encoding" do + let(:blame) do + Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") + end + + it 'converts to UTF-8' do + expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil) + data = [] + blame.each do |commit, line| + data << { commit: commit, line: line - } - end + } + end - expect(data.size).to eq(1) - expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) - expect(data.first[:line]).to eq(" ") - expect(data.first[:line]).to be_utf8 + expect(data.size).to eq(1) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq(" ") + expect(data.first[:line]).to be_utf8 + end end end + + context 'when Gitaly blame feature is enabled' do + it_behaves_like 'blaming a file' + end + + context 'when Gitaly blame feature is disabled', skip_gitaly_mock: true do + it_behaves_like 'blaming a file' + end end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 3c784eda4f8..dfab0c2fe85 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -78,12 +78,18 @@ describe Gitlab::Git::Blob, seed_helper: true do context 'large file' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, 'files/images/6049019_460s.jpg') } let(:blob_size) { 111803 } + let(:stub_limit) { 1000 } + + before do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', stub_limit) + end it { expect(blob.size).to eq(blob_size) } - it { expect(blob.data.length).to eq(blob_size) } + it { expect(blob.data.length).to eq(stub_limit) } it 'check that this test is sane' do - expect(blob.size).to be <= Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + # It only makes sense to test limiting if the blob is larger than the limit. + expect(blob.size).to be > Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE end it 'can load all data' do @@ -146,6 +152,77 @@ describe Gitlab::Git::Blob, seed_helper: true do end end + describe '.batch' do + let(:blob_references) do + [ + [SeedRepo::Commit::ID, "files/ruby/popen.rb"], + [SeedRepo::Commit::ID, 'six'] + ] + end + + subject { described_class.batch(repository, blob_references) } + + it { expect(subject.size).to eq(blob_references.size) } + + context 'first blob' do + let(:blob) { subject[0] } + + it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } + it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) } + it { expect(blob.path).to eq("files/ruby/popen.rb") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) } + it { expect(blob.size).to eq(669) } + it { expect(blob.mode).to eq("100644") } + end + + context 'second blob' do + let(:blob) { subject[1] } + + it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } + it { expect(blob.data).to eq('') } + it 'does not mark the blob as binary' do + expect(blob).not_to be_binary + end + end + + context 'limiting' do + subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) } + + context 'default' do + let(:blob_size_limit) { nil } + + it 'limits to MAX_DATA_DISPLAY_SIZE' do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) + + expect(subject.first.data.size).to eq(100) + end + end + + context 'positive' do + let(:blob_size_limit) { 10 } + + it { expect(subject.first.data.size).to eq(10) } + end + + context 'zero' do + let(:blob_size_limit) { 0 } + + it { expect(subject.first.data).to eq('') } + end + + context 'negative' do + let(:blob_size_limit) { -1 } + + it 'ignores MAX_DATA_DISPLAY_SIZE' do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) + + expect(subject.first.data.size).to eq(669) + end + end + end + end + describe 'encoding' do context 'file with russian text' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 730fdb112d9..c531d4b055f 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Git::Commit, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } - let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) } + let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) } let(:rugged_commit) do repository.rugged.lookup(SeedRepo::Commit::ID) end @@ -24,7 +24,7 @@ describe Gitlab::Git::Commit, seed_helper: true do } @parents = [repo.head.target] - @gitlab_parents = @parents.map { |c| Gitlab::Git::Commit.decorate(c) } + @gitlab_parents = @parents.map { |c| described_class.decorate(repository, c) } @tree = @parents.first.tree sha = Rugged::Commit.create( @@ -38,7 +38,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ) @raw_commit = repo.lookup(sha) - @commit = Gitlab::Git::Commit.new(@raw_commit) + @commit = described_class.new(repository, @raw_commit) end it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) } @@ -66,6 +66,7 @@ describe Gitlab::Git::Commit, seed_helper: true do describe "Commit info from gitaly commit" do let(:id) { 'f00' } + let(:parent_ids) { %w(b45 b46) } let(:subject) { "My commit".force_encoding('ASCII-8BIT') } let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } let(:committer) do @@ -88,10 +89,11 @@ describe Gitlab::Git::Commit, seed_helper: true do subject: subject, body: body, author: author, - committer: committer + committer: committer, + parent_ids: parent_ids ) end - let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) } + let(:commit) { described_class.new(repository, gitaly_commit) } it { expect(commit.short_id).to eq(id[0..10]) } it { expect(commit.id).to eq(id) } @@ -102,6 +104,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(commit.author_name).to eq(author.name) } it { expect(commit.committer_name).to eq(committer.name) } it { expect(commit.committer_email).to eq(committer.email) } + it { expect(commit.parent_ids).to eq(parent_ids) } context 'no body' do let(:body) { "".force_encoding('ASCII-8BIT') } @@ -113,45 +116,45 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'Class methods' do describe '.find' do it "should return first head commit if without params" do - expect(Gitlab::Git::Commit.last(repository).id).to eq( - repository.raw.head.target.oid + expect(described_class.last(repository).id).to eq( + repository.rugged.head.target.oid ) end it "should return valid commit" do - expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_valid_commit + expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit end it "should return valid commit for tag" do - expect(Gitlab::Git::Commit.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') end it "should return nil for non-commit ids" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") - expect(Gitlab::Git::Commit.find(repository, blob.id)).to be_nil + expect(described_class.find(repository, blob.id)).to be_nil end it "should return nil for parent of non-commit object" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") - expect(Gitlab::Git::Commit.find(repository, "#{blob.id}^")).to be_nil + expect(described_class.find(repository, "#{blob.id}^")).to be_nil end it "should return nil for nonexisting ids" do - expect(Gitlab::Git::Commit.find(repository, "+123_4532530XYZ")).to be_nil + expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil end context 'with broken repo' do let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH) } it 'returns nil' do - expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_nil + expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_nil end end end describe '.last_for_path' do context 'no path' do - subject { Gitlab::Git::Commit.last_for_path(repository, 'master') } + subject { described_class.last_for_path(repository, 'master') } describe '#id' do subject { super().id } @@ -160,7 +163,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end context 'path' do - subject { Gitlab::Git::Commit.last_for_path(repository, 'master', 'files/ruby') } + subject { described_class.last_for_path(repository, 'master', 'files/ruby') } describe '#id' do subject { super().id } @@ -169,7 +172,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end context 'ref + path' do - subject { Gitlab::Git::Commit.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') } + subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') } describe '#id' do subject { super().id } @@ -181,7 +184,7 @@ describe Gitlab::Git::Commit, seed_helper: true do describe '.where' do context 'path is empty string' do subject do - commits = Gitlab::Git::Commit.where( + commits = described_class.where( repo: repository, ref: 'master', path: '', @@ -199,7 +202,7 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'path is nil' do subject do - commits = Gitlab::Git::Commit.where( + commits = described_class.where( repo: repository, ref: 'master', path: nil, @@ -217,7 +220,7 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'ref is branch name' do subject do - commits = Gitlab::Git::Commit.where( + commits = described_class.where( repo: repository, ref: 'master', path: 'files', @@ -237,7 +240,7 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'ref is commit id' do subject do - commits = Gitlab::Git::Commit.where( + commits = described_class.where( repo: repository, ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e", path: 'files', @@ -257,7 +260,7 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'ref is tag' do subject do - commits = Gitlab::Git::Commit.where( + commits = described_class.where( repo: repository, ref: 'v1.0.0', path: 'files', @@ -278,7 +281,7 @@ describe Gitlab::Git::Commit, seed_helper: true do describe '.between' do subject do - commits = Gitlab::Git::Commit.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID) + commits = described_class.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID) commits.map { |c| c.id } end @@ -294,12 +297,12 @@ describe Gitlab::Git::Commit, seed_helper: true do it 'should return a return a collection of commits' do commits = described_class.find_all(repository) - expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) + expect(commits).to all( be_a_kind_of(described_class) ) end context 'max_count' do subject do - commits = Gitlab::Git::Commit.find_all( + commits = described_class.find_all( repository, max_count: 50 ) @@ -322,7 +325,7 @@ describe Gitlab::Git::Commit, seed_helper: true do context 'ref + max_count + skip' do subject do - commits = Gitlab::Git::Commit.find_all( + commits = described_class.find_all( repository, ref: 'master', max_count: 50, @@ -374,7 +377,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe '#init_from_rugged' do - let(:gitlab_commit) { Gitlab::Git::Commit.new(rugged_commit) } + let(:gitlab_commit) { described_class.new(repository, rugged_commit) } subject { gitlab_commit } describe '#id' do @@ -384,7 +387,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe '#init_from_hash' do - let(:commit) { Gitlab::Git::Commit.new(sample_commit_hash) } + let(:commit) { described_class.new(repository, sample_commit_hash) } subject { commit } describe '#id' do @@ -451,7 +454,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe '#ref_names' do - let(:commit) { Gitlab::Git::Commit.find(repository, 'master') } + let(:commit) { described_class.find(repository, 'master') } subject { commit.ref_names(repository) } it 'has 1 element' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 50736d353ad..a90c848432e 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -22,7 +22,6 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "Respond to" do subject { repository } - it { is_expected.to respond_to(:raw) } it { is_expected.to respond_to(:rugged) } it { is_expected.to respond_to(:root_ref) } it { is_expected.to respond_to(:tags) } @@ -55,6 +54,20 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#rugged" do + describe 'when storage is broken', broken_storage: true do + it 'raises a storage exception when storage is not available' do + broken_repo = described_class.new('broken', 'a/path.git') + + expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible) + end + end + + it 'raises a no repository exception when there is no repo' do + broken_repo = described_class.new('default', 'a/path.git') + + expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Repository::NoRepository) + end + context 'with no Git env stored' do before do expect(Gitlab::Git::Env).to receive(:all).and_return({}) @@ -361,20 +374,20 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#commit_count' do - shared_examples 'counting commits' do + shared_examples 'simple commit counting' do it { expect(repository.commit_count("master")).to eq(25) } it { expect(repository.commit_count("feature")).to eq(9) } end context 'when Gitaly commit_count feature is enabled' do - it_behaves_like 'counting commits' + it_behaves_like 'simple commit counting' it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do subject { repository.commit_count('master') } end end context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do - it_behaves_like 'counting commits' + it_behaves_like 'simple commit counting' end end @@ -409,11 +422,11 @@ describe Gitlab::Git::Repository, seed_helper: true do it "should fail if we create an existing branch" do @repo.create_branch('duplicated_branch', 'master') - expect{@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") + expect {@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") end it "should fail if we create a branch from a non existing ref" do - expect{@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") + expect {@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") end after(:all) do @@ -492,17 +505,22 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#log" do - commit_with_old_name = nil - commit_with_new_name = nil - rename_commit = nil + let(:commit_with_old_name) do + Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id) + end + let(:commit_with_new_name) do + Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id) + end + let(:rename_commit) do + Gitlab::Git::Commit.decorate(repository, @rename_commit_id) + end before(:context) do # Add new commits so that there's a renamed file in the commit history repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged - - commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo)) - rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo)) - commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo)) + @commit_with_old_name_id = new_commit_edit_old_file(repo) + @rename_commit_id = new_commit_move_file(repo) + @commit_with_new_name_id = new_commit_edit_new_file(repo) end after(:context) do @@ -741,7 +759,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } def commit_files(commit) - commit.diff_from_parent.deltas.flat_map do |delta| + commit.rugged_diff_from_parent.deltas.flat_map do |delta| [delta.old_file[:path], delta.new_file[:path]].uniq.compact end end @@ -757,13 +775,13 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#commits_between" do + describe "#rugged_commits_between" do context 'two SHAs' do let(:first_sha) { 'b0e52af38d7ea43cf41d8a6f2471351ac036d6c9' } let(:second_sha) { '0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326' } it 'returns the number of commits between' do - expect(repository.commits_between(first_sha, second_sha).count).to eq(3) + expect(repository.rugged_commits_between(first_sha, second_sha).count).to eq(3) end end @@ -772,11 +790,11 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:branch) { 'master' } it 'returns the number of commits between a sha and a branch' do - expect(repository.commits_between(sha, branch).count).to eq(5) + expect(repository.rugged_commits_between(sha, branch).count).to eq(5) end it 'returns the number of commits between a branch and a sha' do - expect(repository.commits_between(branch, sha).count).to eq(0) # sha is before branch + expect(repository.rugged_commits_between(branch, sha).count).to eq(0) # sha is before branch end end @@ -785,7 +803,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:second_branch) { 'master' } it 'returns the number of commits between' do - expect(repository.commits_between(first_branch, second_branch).count).to eq(17) + expect(repository.rugged_commits_between(first_branch, second_branch).count).to eq(17) end end end @@ -797,29 +815,39 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#count_commits' do - context 'with after timestamp' do - it 'returns the number of commits after timestamp' do - options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') } + shared_examples 'extended commit counting' do + context 'with after timestamp' do + it 'returns the number of commits after timestamp' do + options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') } - expect(repository.count_commits(options)).to eq(25) + expect(repository.count_commits(options)).to eq(25) + end end - end - context 'with before timestamp' do - it 'returns the number of commits after timestamp' do - options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') } + context 'with before timestamp' do + it 'returns the number of commits before timestamp' do + options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') } - expect(repository.count_commits(options)).to eq(9) + expect(repository.count_commits(options)).to eq(9) + end end - end - context 'with path' do - it 'returns the number of commits with path ' do - options = { ref: 'master', limit: nil, path: "encoding" } + context 'with path' do + it 'returns the number of commits with path ' do + options = { ref: 'master', limit: nil, path: "encoding" } - expect(repository.count_commits(options)).to eq(2) + expect(repository.count_commits(options)).to eq(2) + end end end + + context 'when Gitaly count_commits feature is enabled' do + it_behaves_like 'extended commit counting' + end + + context 'when Gitaly count_commits feature is disabled', skip_gitaly_mock: true do + it_behaves_like 'extended commit counting' + end end describe "branch_names_contains" do @@ -1127,6 +1155,45 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#languages' do + shared_examples 'languages' do + it 'returns exactly the expected results' do + languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6') + expected_languages = [ + { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, + { value: 7.9, label: "HTML", color: "#e44b23", highlight: "#e44b23" }, + { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" } + ] + + expect(languages.size).to eq(expected_languages.size) + + expected_languages.size.times do |i| + a = expected_languages[i] + b = languages[i] + + expect(a.keys.sort).to eq(b.keys.sort) + expect(a[:value]).to be_within(0.1).of(b[:value]) + + non_float_keys = a.keys - [:value] + expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys)) + end + end + + it "uses the repository's HEAD when no ref is passed" do + lang = repository.languages.first + + expect(lang[:label]).to eq('Ruby') + end + end + + it_behaves_like 'languages' + + context 'with rugged', skip_gitaly_mock: true do + it_behaves_like 'languages' + end + end + def create_remote_branch(repository, remote_name, branch_name, source_branch_name) source_branch = repository.branches.find { |branch| branch.name == source_branch_name } rugged = repository.rugged diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb new file mode 100644 index 00000000000..b2886628601 --- /dev/null +++ b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb @@ -0,0 +1,294 @@ +require 'spec_helper' + +describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do + let(:circuit_breaker) { described_class.new('default') } + let(:hostname) { Gitlab::Environment.hostname } + let(:cache_key) { "storage_accessible:default:#{hostname}" } + + def value_from_redis(name) + Gitlab::Git::Storage.redis.with do |redis| + redis.hmget(cache_key, name) + end.first + end + + def set_in_redis(name, value) + Gitlab::Git::Storage.redis.with do |redis| + redis.hmset(cache_key, name, value) + end.first + end + + describe '.reset_all!' do + it 'clears all entries form redis' do + set_in_redis(:failure_count, 10) + + described_class.reset_all! + + key_exists = Gitlab::Git::Storage.redis.with { |redis| redis.exists(cache_key) } + + expect(key_exists).to be_falsey + end + end + + describe '.for_storage' do + it 'only builds a single circuitbreaker per storage' do + expect(described_class).to receive(:new).once.and_call_original + + breaker = described_class.for_storage('default') + + expect(breaker).to be_a(described_class) + expect(described_class.for_storage('default')).to eq(breaker) + end + end + + describe '#initialize' do + it 'assigns the settings' do + expect(circuit_breaker.hostname).to eq(hostname) + expect(circuit_breaker.storage).to eq('default') + expect(circuit_breaker.storage_path).to eq(TestEnv.repos_path) + expect(circuit_breaker.failure_count_threshold).to eq(10) + expect(circuit_breaker.failure_wait_time).to eq(30) + expect(circuit_breaker.failure_reset_time).to eq(1800) + expect(circuit_breaker.storage_timeout).to eq(5) + end + end + + describe '#perform' do + it 'raises an exception with retry time when the circuit is open' do + allow(circuit_breaker).to receive(:circuit_broken?).and_return(true) + + expect { |b| circuit_breaker.perform(&b) } + .to raise_error(Gitlab::Git::Storage::CircuitOpen) + end + + it 'yields the block' do + expect { |b| circuit_breaker.perform(&b) } + .to yield_control + end + + it 'checks if the storage is available' do + expect(circuit_breaker).to receive(:check_storage_accessible!) + + circuit_breaker.perform { 'hello world' } + end + + it 'returns the value of the block' do + result = circuit_breaker.perform { 'return value' } + + expect(result).to eq('return value') + end + + it 'raises possible errors' do + expect { circuit_breaker.perform { raise Rugged::OSError.new('Broken') } } + .to raise_error(Rugged::OSError) + end + + context 'with the feature disabled' do + it 'returns the block without checking accessibility' do + stub_feature_flags(git_storage_circuit_breaker: false) + + expect(circuit_breaker).not_to receive(:circuit_broken?) + + result = circuit_breaker.perform { 'hello' } + + expect(result).to eq('hello') + end + end + end + + describe '#circuit_broken?' do + it 'is closed when there is no last failure' do + set_in_redis(:last_failure, nil) + set_in_redis(:failure_count, 0) + + expect(circuit_breaker.circuit_broken?).to be_falsey + end + + it 'is open when there was a recent failure' do + Timecop.freeze do + set_in_redis(:last_failure, 1.second.ago.to_f) + set_in_redis(:failure_count, 1) + + expect(circuit_breaker.circuit_broken?).to be_truthy + end + end + + it 'is open when there are to many failures' do + set_in_redis(:last_failure, 1.day.ago.to_f) + set_in_redis(:failure_count, 200) + + expect(circuit_breaker.circuit_broken?).to be_truthy + end + end + + describe "storage_available?" do + context 'when the storage is available' do + it 'tracks that the storage was accessible an raises the error' do + expect(circuit_breaker).to receive(:track_storage_accessible) + + circuit_breaker.storage_available? + end + + it 'only performs the check once' do + expect(Gitlab::Git::Storage::ForkedStorageCheck) + .to receive(:storage_available?).once.and_call_original + + 2.times { circuit_breaker.storage_available? } + end + end + + context 'when storage is not available' do + let(:circuit_breaker) { described_class.new('broken') } + + it 'tracks that the storage was inaccessible' do + expect(circuit_breaker).to receive(:track_storage_inaccessible) + + circuit_breaker.storage_available? + end + end + end + + describe '#check_storage_accessible!' do + it 'raises an exception with retry time when the circuit is open' do + allow(circuit_breaker).to receive(:circuit_broken?).and_return(true) + + expect { circuit_breaker.check_storage_accessible! } + .to raise_error do |exception| + expect(exception).to be_kind_of(Gitlab::Git::Storage::CircuitOpen) + expect(exception.retry_after).to eq(30) + end + end + + context 'when the storage is not available' do + let(:circuit_breaker) { described_class.new('broken') } + + it 'raises an error' do + expect(circuit_breaker).to receive(:track_storage_inaccessible) + + expect { circuit_breaker.check_storage_accessible! } + .to raise_error do |exception| + expect(exception).to be_kind_of(Gitlab::Git::Storage::Inaccessible) + expect(exception.retry_after).to eq(30) + end + end + end + end + + describe '#track_storage_inaccessible' do + around(:each) do |example| + Timecop.freeze + + example.run + + Timecop.return + end + + it 'records the failure time in redis' do + circuit_breaker.track_storage_inaccessible + + failure_time = value_from_redis(:last_failure) + + expect(Time.at(failure_time.to_i)).to be_within(1.second).of(Time.now) + end + + it 'sets the failure time on the breaker without reloading' do + circuit_breaker.track_storage_inaccessible + + expect(circuit_breaker).not_to receive(:get_failure_info) + expect(circuit_breaker.last_failure).to eq(Time.now) + end + + it 'increments the failure count in redis' do + set_in_redis(:failure_count, 10) + + circuit_breaker.track_storage_inaccessible + + expect(value_from_redis(:failure_count).to_i).to be(11) + end + + it 'increments the failure count on the breaker without reloading' do + set_in_redis(:failure_count, 10) + + circuit_breaker.track_storage_inaccessible + + expect(circuit_breaker).not_to receive(:get_failure_info) + expect(circuit_breaker.failure_count).to eq(11) + end + end + + describe '#track_storage_accessible' do + it 'sets the failure count to zero in redis' do + set_in_redis(:failure_count, 10) + + circuit_breaker.track_storage_accessible + + expect(value_from_redis(:failure_count).to_i).to be(0) + end + + it 'sets the failure count to zero on the breaker without reloading' do + set_in_redis(:failure_count, 10) + + circuit_breaker.track_storage_accessible + + expect(circuit_breaker).not_to receive(:get_failure_info) + expect(circuit_breaker.failure_count).to eq(0) + end + + it 'removes the last failure time from redis' do + set_in_redis(:last_failure, Time.now.to_i) + + circuit_breaker.track_storage_accessible + + expect(circuit_breaker).not_to receive(:get_failure_info) + expect(circuit_breaker.last_failure).to be_nil + end + + it 'removes the last failure time from the breaker without reloading' do + set_in_redis(:last_failure, Time.now.to_i) + + circuit_breaker.track_storage_accessible + + expect(value_from_redis(:last_failure)).to be_empty + end + + it 'wont connect to redis when there are no failures' do + expect(Gitlab::Git::Storage.redis).to receive(:with).once + .and_call_original + expect(circuit_breaker).to receive(:track_storage_accessible) + .and_call_original + + circuit_breaker.track_storage_accessible + end + end + + describe '#no_failures?' do + it 'is false when a failure was tracked' do + set_in_redis(:last_failure, Time.now.to_i) + set_in_redis(:failure_count, 1) + + expect(circuit_breaker.no_failures?).to be_falsey + end + end + + describe '#last_failure' do + it 'returns the last failure time' do + time = Time.parse("2017-05-26 17:52:30") + set_in_redis(:last_failure, time.to_i) + + expect(circuit_breaker.last_failure).to eq(time) + end + end + + describe '#failure_count' do + it 'returns the failure count' do + set_in_redis(:failure_count, 7) + + expect(circuit_breaker.failure_count).to eq(7) + end + end + + describe '#cache_key' do + it 'includes storage and host' do + expect(circuit_breaker.cache_key).to eq(cache_key) + end + end +end diff --git a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb new file mode 100644 index 00000000000..12366151f44 --- /dev/null +++ b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Gitlab::Git::Storage::ForkedStorageCheck, skip_database_cleaner: true do + let(:existing_path) do + existing_path = TestEnv.repos_path + FileUtils.mkdir_p(existing_path) + existing_path + end + + describe '.storage_accessible?' do + it 'detects when a storage is not available' do + expect(described_class.storage_available?('/non/existant/path')).to be_falsey + end + + it 'detects when a storage is available' do + expect(described_class.storage_available?(existing_path)).to be_truthy + end + + it 'returns false when the check takes to long' do + # We're forking a process here that takes too long + # It will be killed it's parent process will be killed by it's parent + # and waited for inside `Gitlab::Git::Storage::ForkedStorageCheck.timeout_check` + allow(described_class).to receive(:check_filesystem_in_process) do + Process.spawn("sleep 10") + end + result = true + + runtime = Benchmark.realtime do + result = described_class.storage_available?(existing_path, 0.5) + end + + expect(result).to be_falsey + expect(runtime).to be < 1.0 + end + + describe 'when using paths with spaces' do + let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') } + let(:path_with_spaces) { File.join(test_dir, 'path with spaces') } + + around do |example| + FileUtils.mkdir_p(path_with_spaces) + example.run + FileUtils.rm_r(test_dir) + end + + it 'works for paths with spaces' do + expect(described_class.storage_available?(path_with_spaces)).to be_truthy + end + + it 'works for a realpath with spaces' do + symlink_location = File.join(test_dir, 'a symlink') + FileUtils.ln_s(path_with_spaces, symlink_location) + + expect(described_class.storage_available?(symlink_location)).to be_truthy + end + end + end +end diff --git a/spec/lib/gitlab/git/storage/health_spec.rb b/spec/lib/gitlab/git/storage/health_spec.rb new file mode 100644 index 00000000000..2d3af387971 --- /dev/null +++ b/spec/lib/gitlab/git/storage/health_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, broken_storage: true do + let(:host1_key) { 'storage_accessible:broken:web01' } + let(:host2_key) { 'storage_accessible:default:kiq01' } + + def set_in_redis(cache_key, value) + Gitlab::Git::Storage.redis.with do |redis| + redis.hmset(cache_key, :failure_count, value) + end.first + end + + describe '.for_failing_storages' do + it 'only includes health status for failures' do + set_in_redis(host1_key, 10) + set_in_redis(host2_key, 0) + + expect(described_class.for_failing_storages.map(&:storage_name)) + .to contain_exactly('broken') + end + end + + describe '.load_for_keys' do + let(:subject) do + results = Gitlab::Git::Storage.redis.with do |redis| + fake_future = double + allow(fake_future).to receive(:value).and_return([host1_key]) + described_class.load_for_keys({ 'broken' => fake_future }, redis) + end + + # Make sure the `Redis#future is loaded + results.inject({}) do |result, (name, info)| + info.each { |i| i[:failure_count] = i[:failure_count].value.to_i } + + result[name] = info + + result + end + end + + it 'loads when there is no info in redis' do + expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 0 }]) + end + + it 'reads the correct values for a storage from redis' do + set_in_redis(host1_key, 5) + set_in_redis(host2_key, 7) + + expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 5 }]) + end + end + + describe '.for_all_storages' do + it 'loads health status for all configured storages' do + healths = described_class.for_all_storages + + expect(healths.map(&:storage_name)).to contain_exactly('default', 'broken') + end + end + + describe '#failing_info' do + it 'only contains storages that have failures' do + health = described_class.new('broken', [{ name: host1_key, failure_count: 0 }, + { name: host2_key, failure_count: 3 }]) + + expect(health.failing_info).to contain_exactly({ name: host2_key, failure_count: 3 }) + end + end + + describe '#total_failures' do + it 'sums up all the failures' do + health = described_class.new('broken', [{ name: host1_key, failure_count: 2 }, + { name: host2_key, failure_count: 3 }]) + + expect(health.total_failures).to eq(5) + end + end + + describe '#failing_on_hosts' do + it 'collects only the failing hostnames' do + health = described_class.new('broken', [{ name: host1_key, failure_count: 2 }, + { name: host2_key, failure_count: 0 }]) + + expect(health.failing_on_hosts).to contain_exactly('web01') + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 0868c793a33..7fe698fcb18 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::GitalyClient::CommitService do let(:project) { create(:project, :repository) } let(:storage_name) { project.repository_storage } - let(:relative_path) { project.path_with_namespace + '.git' } + let(:relative_path) { project.disk_path + '.git' } let(:repository) { project.repository } let(:repository_message) { repository.gitaly_repository } let(:revision) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } @@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::CommitService do context 'when a commit does not have a parent' do it 'sends an RPC request with empty tree ref as left commit' do - initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') + initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw request = Gitaly::CommitDiffRequest.new( repository: repository_message, left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', @@ -46,18 +46,10 @@ describe Gitlab::GitalyClient::CommitService do end end - it 'returns a Gitlab::Git::DiffCollection' do + it 'returns a Gitlab::GitalyClient::DiffStitcher' do ret = client.diff_from_parent(commit) - expect(ret).to be_kind_of(Gitlab::Git::DiffCollection) - end - - it 'passes options to Gitlab::Git::DiffCollection' do - options = { max_files: 31, max_lines: 13, from_gitaly: true } - - expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options) - - client.diff_from_parent(commit, options) + expect(ret).to be_kind_of(Gitlab::GitalyClient::DiffStitcher) end end @@ -120,4 +112,18 @@ describe Gitlab::GitalyClient::CommitService do client.tree_entries(repository, revision, path) end end + + describe '#find_commit' do + let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + it 'sends an RPC request' do + request = Gitaly::FindCommitRequest.new( + repository: repository_message, revision: revision + ) + + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit) + .with(request, kind_of(Hash)).and_return(double(commit: nil)) + + described_class.new(repository).find_commit(revision) + end + end end diff --git a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb index d9597c4aa78..ffc3a09be30 100644 --- a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Gitlab::GitalyClient::NotificationService do describe '#post_receive' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:storage_name) { project.repository_storage } - let(:relative_path) { project.path_with_namespace + '.git' } + let(:relative_path) { project.disk_path + '.git' } subject { described_class.new(project.repository) } it 'sends a post_receive message' do diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 0b1c890f956..46efc1b18f0 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Gitlab::GitalyClient::RefService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:storage_name) { project.repository_storage } - let(:relative_path) { project.path_with_namespace + '.git' } + let(:relative_path) { project.disk_path + '.git' } let(:client) { described_class.new(project.repository) } describe '#branches' do diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb deleted file mode 100644 index 5a9f3fc130c..00000000000 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe Gitlab::GitalyClient::RepositoryService do - set(:project) { create(:empty_project) } - let(:storage_name) { project.repository_storage } - let(:relative_path) { project.path_with_namespace + '.git' } - let(:client) { described_class.new(project.repository) } - - describe '#exists?' do - it 'sends an exists message' do - expect_any_instance_of(Gitaly::RepositoryService::Stub) - .to receive(:exists) - .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) - .and_call_original - - client.exists? - end - end -end diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index ef89634685a..035ac8c7c1f 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GithubImport::CommentFormatter do let(:client) { double } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') } let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') } let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') } diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index d00a2deaf7b..d570f34985b 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -207,7 +207,7 @@ describe Gitlab::GithubImport::Importer do end end - let(:project) { create(:project, :wiki_disabled, import_url: "#{repo_root}/octocat/Hello-World.git") } + let(:project) { create(:project, :repository, :wiki_disabled, import_url: "#{repo_root}/octocat/Hello-World.git") } let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') } let(:credentials) { { user: 'joe' } } diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index 39b15926193..0fc56d92aa6 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GithubImport::IssueFormatter do let(:client) { double } - let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb index 2cc7ac0b446..83fdd2cc415 100644 --- a/spec/lib/gitlab/github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::LabelFormatter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:raw) { double(name: 'improvements', color: 'e6e6e6') } subject { described_class.new(project, raw) } diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb index 310e0536fd7..683fa51b78e 100644 --- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::MilestoneFormatter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:base_data) do diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb index 1357cb636ae..926bf725d6a 100644 --- a/spec/lib/gitlab/github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::ReleaseFormatter do - let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb index de50265bc14..fcd90fab547 100644 --- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb @@ -9,9 +9,9 @@ describe Gitlab::GithubImport::WikiFormatter do subject(:wiki) { described_class.new(project) } - describe '#path_with_namespace' do + describe '#disk_path' do it 'appends .wiki to project path' do - expect(wiki.path_with_namespace).to eq 'gitlabhq/gitlabhq.wiki' + expect(wiki.disk_path).to eq project.disk_path + '.wiki' end end diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb index 16b14474b89..e1d935602b5 100644 --- a/spec/lib/gitlab/gitlab_import/importer_spec.rb +++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::GitlabImport::Importer do end it 'persists issues' do - project = create(:empty_project, import_source: 'asd/vim') + project = create(:project, import_source: 'asd/vim') project.build_import_data(credentials: { password: 'password' }) subject = described_class.new(project) diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index da48d8f0670..82548c7fd31 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GitlabImport::ProjectCreator do owner: { name: "john" } }.with_indifferent_access end - let(:namespace){ create(:group, owner: user) } + let(:namespace) { create(:group, owner: user) } let(:token) { "asdffg" } let(:access_params) { { gitlab_access_token: token } } diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index ac3558ab386..4e09020471b 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ::Gitlab::GlRepository do describe '.parse' do - set(:project) { create(:project) } + set(:project) { create(:project, :repository) } it 'parses a project gl_repository' do expect(described_class.parse("project-#{project.id}")).to eq([project, false]) diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 85f40825005..798ea0bac58 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::GoogleCodeImport::Importer do 'user_map' => { 'thilo...' => "@#{mapped_user.username}" } } end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { described_class.new(project) } diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb index aad53938d52..8d5b60d50de 100644 --- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator do "repositoryUrls" => ["https://vim.googlecode.com/git/"] ) end - let(:namespace){ create(:group, owner: user) } + let(:namespace) { create(:group, owner: user) } before do namespace.add_owner(user) diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb index 3f9382a9143..b2084f56640 100644 --- a/spec/lib/gitlab/graphs/commits_spec.rb +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Graphs::Commits do - let!(:project) { create(:empty_project, :public) } + let!(:project) { create(:project, :public) } let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 8abc4320c59..26574df8bb5 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -44,6 +44,15 @@ describe Gitlab::HealthChecks::FsShardsCheck do describe '#readiness' do subject { described_class.readiness } + context 'storage has a tripped circuitbreaker', broken_storage: true do + let(:repository_storages) { ['broken'] } + let(:storages_paths) do + Gitlab.config.repositories.storages + end + + it { is_expected.to include(result_class.new(false, 'circuitbreaker tripped', shard: 'broken')) } + end + context 'storage points to not existing folder' do let(:storages_paths) do { @@ -51,6 +60,10 @@ describe Gitlab::HealthChecks::FsShardsCheck do }.with_indifferent_access end + before do + allow(described_class).to receive(:storage_circuitbreaker_test) { true } + end + it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) } end @@ -109,6 +122,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) + expect(metrics).to include(an_object_having_attributes(name: :filesystem_circuitbreaker_latency_seconds, value: be >= 0)) end end @@ -127,6 +141,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) + expect(metrics).to include(an_object_having_attributes(name: :filesystem_circuitbreaker_latency_seconds, value: be >= 0)) end it 'cleans up files used for metrics' do diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb index 29912da2e25..cfaeb1f0d4f 100644 --- a/spec/lib/gitlab/identifier_spec.rb +++ b/spec/lib/gitlab/identifier_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Identifier do Class.new { include Gitlab::Identifier }.new end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:key) { create(:key, user: user) } diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb index 574748756bd..cd5a1b2982b 100644 --- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AttributeCleaner do - let(:relation_class){ double('relation_class').as_null_object } + let(:relation_class) { double('relation_class').as_null_object } let(:unsafe_hash) do { 'id' => 101, diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index a7b292c8558..a93a921e459 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ImportExport::AvatarRestorer do include UploadHelpers let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do allow_any_instance_of(described_class).to receive(:avatar_export_file) diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 814f85de03b..3fb5ddde8b5 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } - let(:project) { create(:empty_project) } + let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let(:project) { create(:project) } before do FileUtils.mkdir_p("#{shared.export_path}/avatar/") diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 08588a76fe6..c7fbc2bc92f 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' describe 'forked project import' do let(:user) { create(:user) } - let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') } - let!(:project) { create(:empty_project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } + let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } + let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } - let(:forked_from_project) { create(:project) } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:forked_from_project) { create(:project, :repository) } let(:fork_link) { create(:forked_project_link, forked_from_project: project_with_repo) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb index 07415d41f93..40a5f2294a2 100644 --- a/spec/lib/gitlab/import_export/import_export_spec.rb +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport do describe 'export filename' do let(:group) { create(:group, :nested) } - let(:project) { create(:empty_project, :public, path: 'project-path', namespace: group) } + let(:project) { create(:project, :public, path: 'project-path', namespace: group) } it 'contains the project path' do expect(described_class.export_filename(project: project)).to include(project.path) diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index f66a2ab7dda..246f009ad27 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper do describe 'map members' do let(:user) { create(:admin) } - let(:project) { create(:empty_project, :public, name: 'searchable_project') } + let(:project) { create(:project, :public, name: 'searchable_project') } let(:user2) { create(:user) } let(:exported_user_id) { 99 } let(:exported_members) do @@ -96,7 +96,7 @@ describe Gitlab::ImportExport::MembersMapper do context 'importer same as group member' do let(:user2) { create(:admin) } let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) } + let(:project) { create(:project, :public, name: 'searchable_project', namespace: group) } let(:members_mapper) do described_class.new( exported_members: exported_members, user: user2, project: project) @@ -119,7 +119,7 @@ describe Gitlab::ImportExport::MembersMapper do context 'importing group members' do let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let(:members_mapper) do described_class.new( exported_members: exported_members, user: user, project: project) diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index f2b66c4421c..4d87f27ce05 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::ImportExport::MergeRequestParser do let(:user) { create(:user) } - let!(:project) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') } - let(:forked_from_project) { create(:project) } + let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } + let(:forked_from_project) { create(:project, :repository) } let(:fork_link) { create(:forked_project_link, forked_from_project: project) } let!(:merge_request) do diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 469a014e4d2..4e631e13410 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2534,7 +2534,6 @@ "iid": 9, "description": null, "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -2983,7 +2982,6 @@ "iid": 8, "description": null, "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -3267,7 +3265,6 @@ "iid": 7, "description": "Et commodi deserunt aspernatur vero rerum. Ut non dolorum alias in odit est libero. Voluptatibus eos in et vitae repudiandae facilis ex mollitia.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -3551,7 +3548,6 @@ "iid": 6, "description": "Dicta magnam non voluptates nam dignissimos nostrum deserunt. Dolorum et suscipit iure quae doloremque. Necessitatibus saepe aut labore sed.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -4241,7 +4237,6 @@ "iid": 5, "description": "Est eaque quasi qui qui. Similique voluptatem impedit iusto ratione reprehenderit. Itaque est illum ut nulla aut.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -4789,7 +4784,6 @@ "iid": 4, "description": "Nam magnam odit velit rerum. Sapiente dolore sunt saepe debitis. Culpa maiores ut ad dolores dolorem et.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -5288,7 +5282,6 @@ "iid": 3, "description": "Libero nesciunt mollitia quis odit eos vero quasi. Iure voluptatem ut sint pariatur voluptates ut aut. Laborum possimus unde illum ipsum eum.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -5548,7 +5541,6 @@ "iid": 2, "description": "Ut dolor quia aliquid dolore et nisi. Est minus suscipit enim quaerat sapiente consequatur rerum. Eveniet provident consequatur dolor accusantium reiciendis.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -6238,7 +6230,6 @@ "iid": 1, "description": "Eveniet nihil ratione veniam similique qui aut sapiente tempora. Sed praesentium iusto dignissimos possimus id repudiandae quo nihil. Qui doloremque autem et iure fugit.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index d1ec0e45bbd..7ee0e22f28d 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do RSpec::Mocks.with_temporary_scope do @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') - @project = create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') + @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) @restored_project_json = project_tree_restorer.restore end @@ -178,7 +178,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Light JSON' do let(:user) { create(:user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } - let!(:project) { create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } + let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } @@ -210,7 +210,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'with group' do let!(:project) do - create(:empty_project, + create(:project, :builds_disabled, :issues_disabled, name: 'project', diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 0c7e733b01f..a278f89c1a1 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do describe 'saves the project tree into a json object' do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index baa90af84f7..f1df44cea75 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::RelationFactory do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:members_mapper) { double('members_mapper').as_null_object } let(:user) { create(:admin) } let(:created_object) do diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index d3be2965bf4..2786bc92fe5 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoRestorer do describe 'bundle a project Git repo' do let(:user) { create(:user) } - let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') } - let!(:project) { create(:empty_project) } + let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } + let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index 87af13e0beb..e6ad516deef 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoSaver do describe 'bundle a project Git repo' do let(:user) { create(:user) } - let!(:project) { create(:empty_project, :public, name: 'searchable_project') } + let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:bundler) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 11f4c16ff96..4dce48f8079 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -145,7 +145,6 @@ MergeRequest: - iid - description - position -- locked_at - updated_by_id - merge_error - merge_params diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index 78137aeff5e..0e55993c8ef 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Gitlab::ImportExport::WikiRepoSaver do describe 'bundle a wiki Git repo' do let(:user) { create(:user) } - let!(:project) { create(:empty_project, :public, name: 'searchable_project') } + let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } let!(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index b3b5e5e7e33..c5725f47453 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -56,7 +56,7 @@ describe Gitlab::ImportSources do describe '.importer' do import_sources = { - 'github' => Gitlab::GithubImport::Importer, + 'github' => Github::Import, 'bitbucket' => Gitlab::BitbucketImport::Importer, 'gitlab' => Gitlab::GitlabImport::Importer, 'google_code' => Gitlab::GoogleCodeImport::Importer, diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb index aeb32ef96d6..642a6cb6caa 100644 --- a/spec/lib/gitlab/issuable_sorter_spec.rb +++ b/spec/lib/gitlab/issuable_sorter_spec.rb @@ -1,25 +1,25 @@ require 'spec_helper' describe Gitlab::IssuableSorter do - let(:namespace1) { build(:namespace, id: 1) } - let(:project1) { build(:project, id: 1, namespace: namespace1) } + let(:namespace1) { build_stubbed(:namespace, id: 1) } + let(:project1) { build_stubbed(:project, id: 1, namespace: namespace1) } - let(:project2) { build(:project, id: 2, path: "a", namespace: project1.namespace) } - let(:project3) { build(:project, id: 3, path: "b", namespace: project1.namespace) } + let(:project2) { build_stubbed(:project, id: 2, path: "a", namespace: project1.namespace) } + let(:project3) { build_stubbed(:project, id: 3, path: "b", namespace: project1.namespace) } - let(:namespace2) { build(:namespace, id: 2, path: "a") } - let(:namespace3) { build(:namespace, id: 3, path: "b") } - let(:project4) { build(:project, id: 4, path: "a", namespace: namespace2) } - let(:project5) { build(:project, id: 5, path: "b", namespace: namespace2) } - let(:project6) { build(:project, id: 6, path: "a", namespace: namespace3) } + let(:namespace2) { build_stubbed(:namespace, id: 2, path: "a") } + let(:namespace3) { build_stubbed(:namespace, id: 3, path: "b") } + let(:project4) { build_stubbed(:project, id: 4, path: "a", namespace: namespace2) } + let(:project5) { build_stubbed(:project, id: 5, path: "b", namespace: namespace2) } + let(:project6) { build_stubbed(:project, id: 6, path: "a", namespace: namespace3) } let(:unsorted) { [sorted[2], sorted[3], sorted[0], sorted[1]] } let(:sorted) do - [build(:issue, iid: 1, project: project1), - build(:issue, iid: 2, project: project1), - build(:issue, iid: 10, project: project1), - build(:issue, iid: 20, project: project1)] + [build_stubbed(:issue, iid: 1, project: project1), + build_stubbed(:issue, iid: 2, project: project1), + build_stubbed(:issue, iid: 10, project: project1), + build_stubbed(:issue, iid: 20, project: project1)] end it 'sorts references by a given key' do @@ -41,14 +41,14 @@ describe Gitlab::IssuableSorter do context 'for references from multiple projects and namespaces' do let(:sorted) do - [build(:issue, iid: 1, project: project1), - build(:issue, iid: 2, project: project1), - build(:issue, iid: 10, project: project1), - build(:issue, iid: 1, project: project2), - build(:issue, iid: 1, project: project3), - build(:issue, iid: 1, project: project4), - build(:issue, iid: 1, project: project5), - build(:issue, iid: 1, project: project6)] + [build_stubbed(:issue, iid: 1, project: project1), + build_stubbed(:issue, iid: 2, project: project1), + build_stubbed(:issue, iid: 10, project: project1), + build_stubbed(:issue, iid: 1, project: project2), + build_stubbed(:issue, iid: 1, project: project3), + build_stubbed(:issue, iid: 1, project: project4), + build_stubbed(:issue, iid: 1, project: project5), + build_stubbed(:issue, iid: 1, project: project6)] end let(:unsorted) do [sorted[3], sorted[1], sorted[4], sorted[2], diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb index d7bebaca675..f5fd5a96bc9 100644 --- a/spec/lib/gitlab/key_fingerprint_spec.rb +++ b/spec/lib/gitlab/key_fingerprint_spec.rb @@ -1,12 +1,82 @@ -require "spec_helper" +require 'spec_helper' -describe Gitlab::KeyFingerprint do - let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" } - let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } +describe Gitlab::KeyFingerprint, lib: true do + KEYS = { + rsa: + 'example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5z65PwQ1GE6foJgwk' \ + '9rmQi/glaXbUeVa5uvQpnZ3Z5+forcI7aTngh3aZ/H2UDP2L70TGy7kKNyp0J3a8/OdG' \ + 'Z08y5yi3JlbjFARO1NyoFEjw2H1SJxeJ43L6zmvTlu+hlK1jSAlidl7enS0ufTlzEEj4' \ + 'iJcuTPKdVzKRgZuTRVm9woWNVKqIrdRC0rJiTinERnfSAp/vNYERMuaoN4oJt8p/NEek' \ + 'rmFoDsQOsyDW5RAnCnjWUU+jFBKDpfkJQ1U2n6BjJewC9dl6ODK639l3yN4WOLZEk4tN' \ + 'UysfbGeF3rmMeflaD6O1Jplpv3YhwVGFNKa7fMq6k3Z0tszTJPYh', + ecdsa: + 'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAI' \ + 'bmlzdHAyNTYAAABBBKTJy43NZzJSfNxpv/e2E6Zy3qoHoTQbmOsU5FEfpWfWa1MdTeXQ' \ + 'YvKOi+qz/1AaNx6BK421jGu74JCDJtiZWT8=', + ed25519: + '@revoked example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjq' \ + 'uxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf', + dss: + 'example.com ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4EddRIpUt9KnC7s5Of2EbdS' \ + 'PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f' \ + '6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iID' \ + 'GZ3RSAHHAAAAFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QAAAIEA9+GghdabPd7LvKtcNrhX' \ + 'uXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwW' \ + 'eotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6A' \ + 'e1UlZAFMO/7PSSoAAACBAJcQ4JODqhuGbXIEpqxetm7PWbdbCcr3y/GzIZ066pRovpL6' \ + 'qm3qCVIym4cyChxWwb8qlyCIi+YRUUWm1z/wiBYT2Vf3S4FXBnyymCkKEaV/EY7+jd4X' \ + '1bXI58OD2u+bLCB/sInM4fGB8CZUIWT9nJH0Ve9jJUge2ms348/QOJ1+' + }.freeze - describe "#fingerprint" do - it "generates the key's fingerprint" do - expect(described_class.new(key).fingerprint).to eq(fingerprint) + MD5_FINGERPRINTS = { + rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd', + ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e', + ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16', + dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b' + }.freeze + + BIT_COUNTS = { + rsa: 2048, + ecdsa: 256, + ed25519: 256, + dss: 1024 + }.freeze + + describe '#type' do + KEYS.each do |type, key| + it "calculates the type of #{type} keys" do + calculated_type = described_class.new(key).type + + expect(calculated_type).to eq(type.to_s.upcase) + end + end + end + + describe '#fingerprint' do + KEYS.each do |type, key| + it "calculates the MD5 fingerprint for #{type} keys" do + fp = described_class.new(key).fingerprint + + expect(fp).to eq(MD5_FINGERPRINTS[type]) + end + end + end + + describe '#bits' do + KEYS.each do |type, key| + it "calculates the number of bits in #{type} keys" do + bits = described_class.new(key).bits + + expect(bits).to eq(BIT_COUNTS[type]) + end + end + end + + describe '#key' do + it 'carries the unmodified key data' do + key = described_class.new(KEYS[:rsa]).key + + expect(key).to eq(KEYS[:rsa]) end end end diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index 292ec064a67..ca2213cd112 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::LDAP::Config do describe '#initialize' do it 'requires a provider' do - expect{ described_class.new }.to raise_error ArgumentError + expect { described_class.new }.to raise_error ArgumentError end it 'works' do @@ -15,7 +15,7 @@ describe Gitlab::LDAP::Config do end it 'raises an error if a unknown provider is used' do - expect{ described_class.new 'unknown' }.to raise_error(RuntimeError) + expect { described_class.new 'unknown' }.to raise_error(RuntimeError) end end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 175ceec44d7..5100a5a609e 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -61,12 +61,12 @@ describe Gitlab::LDAP::User do it "finds the user if already existing" do create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') - expect{ ldap_user.save }.not_to change{ User.count } + expect { ldap_user.save }.not_to change { User.count } end it "connects to existing non-ldap user if the email matches" do existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") - expect{ ldap_user.save }.not_to change{ User.count } + expect { ldap_user.save }.not_to change { User.count } existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' @@ -75,7 +75,7 @@ describe Gitlab::LDAP::User do it 'connects to existing ldap user if the extern_uid changes' do existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect{ ldap_user.save }.not_to change{ User.count } + expect { ldap_user.save }.not_to change { User.count } existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' @@ -85,7 +85,7 @@ describe Gitlab::LDAP::User do it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect{ ldap_user_upper_case.save }.not_to change{ User.count } + expect { ldap_user_upper_case.save }.not_to change { User.count } existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' @@ -106,7 +106,7 @@ describe Gitlab::LDAP::User do end it "creates a new user if not found" do - expect{ ldap_user.save }.to change{ User.count }.by(1) + expect { ldap_user.save }.to change { User.count }.by(1) end context 'when signup is disabled' do diff --git a/spec/lib/gitlab/metrics/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb index 0bc68d64276..999a9536d82 100644 --- a/spec/lib/gitlab/metrics/influx_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Metrics::InfluxSampler do it 'runs once and gathers a sample at a given interval' do expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice expect(sampler).to receive(:sample).once - expect(sampler).to receive(:running).and_return(false, true, false) + expect(sampler).to receive(:running).and_return(true, false) sampler.start.join end diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb new file mode 100644 index 00000000000..6721e02fb85 --- /dev/null +++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe Gitlab::Metrics::SidekiqMetricsExporter do + let(:exporter) { described_class.new } + let(:server) { double('server') } + + before do + allow(::WEBrick::HTTPServer).to receive(:new).and_return(server) + allow(server).to receive(:mount) + allow(server).to receive(:start) + allow(server).to receive(:shutdown) + end + + describe 'when exporter is enabled' do + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(true) + end + + describe 'when exporter is stopped' do + describe '#start' do + it 'starts the exporter' do + expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true) + + expect(server).to have_received(:start) + end + + describe 'with custom settings' do + let(:port) { 99999 } + let(:address) { 'sidekiq_exporter_address' } + + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:port).and_return(port) + allow(Settings.monitoring.sidekiq_exporter).to receive(:address).and_return(address) + end + + it 'starts server with port and address from settings' do + exporter.start.join + + expect(::WEBrick::HTTPServer).to have_received(:new).with( + Port: port, + BindAddress: address + ) + end + end + end + + describe '#stop' do + it "doesn't shutdown stopped server" do + expect { exporter.stop }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:shutdown) + end + end + end + + describe 'when exporter is running' do + before do + exporter.start.join + end + + describe '#start' do + it "doesn't start running server" do + expect { exporter.start.join }.not_to change { exporter.thread? } + + expect(server).to have_received(:start).once + end + end + + describe '#stop' do + it 'shutdowns server' do + expect { exporter.stop }.to change { exporter.thread? }.from(true).to(false) + + expect(server).to have_received(:shutdown) + end + end + end + end + + describe 'when exporter is disabled' do + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(false) + end + + describe '#start' do + it "doesn't start" do + expect(exporter.start).to be_nil + expect { exporter.start }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:start) + end + end + + describe '#stop' do + it "doesn't shutdown" do + expect { exporter.stop }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:shutdown) + end + end + end +end diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 47aa19d5fd9..15edb820908 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -147,7 +147,7 @@ describe Gitlab::OAuth::User do end it 'throws an error' do - expect{ oauth_user.save }.to raise_error StandardError + expect { oauth_user.save }.to raise_error StandardError end end @@ -157,7 +157,7 @@ describe Gitlab::OAuth::User do end it 'throws an error' do - expect{ oauth_user.save }.to raise_error StandardError + expect { oauth_user.save }.to raise_error StandardError end end end @@ -457,4 +457,34 @@ describe Gitlab::OAuth::User do end end end + + describe 'generating username' do + context 'when no collision with existing user' do + it 'generates the username with no counter' do + expect(gl_user.username).to eq('johngitlab-ETC') + end + end + + context 'when collision with existing user' do + it 'generates the username with a counter' do + oauth_user.save + oauth_user2 = described_class.new(OmniAuth::AuthHash.new(uid: 'my-uid2', provider: provider, info: { nickname: 'johngitlab-ETC@othermail.com', email: 'john@othermail.com' })) + + expect(oauth_user2.gl_user.username).to eq('johngitlab-ETC1') + end + end + + context 'when username is a reserved word' do + let(:info_hash) do + { + nickname: 'admin@othermail.com', + email: 'admin@othermail.com' + } + end + + it 'generates the username with a counter' do + expect(gl_user.username).to eq('admin1') + end + end + end end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 9ce33685697..953cfbb8b88 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Gitlab::ProjectAuthorizations do let(:group) { create(:group) } - let!(:owned_project) { create(:empty_project) } - let!(:other_project) { create(:empty_project) } - let!(:group_project) { create(:empty_project, namespace: group) } + let!(:owned_project) { create(:project) } + let!(:other_project) { create(:project) } + let!(:group_project) { create(:project, namespace: group) } let(:user) { owned_project.namespace.owner } @@ -49,7 +49,7 @@ describe Gitlab::ProjectAuthorizations do if Group.supports_nested_groups? context 'with nested groups' do let!(:nested_group) { create(:group, parent: group) } - let!(:nested_project) { create(:empty_project, namespace: nested_group) } + let!(:nested_project) { create(:project, namespace: nested_group) } it 'includes nested groups' do expect(authorizations.pluck(:project_id)).to include(nested_project.id) diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index d17b436b910..9c3e7d7e9ba 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ProjectSearchResults do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:query) { 'hello world' } describe 'initialize with empty ref' do @@ -154,7 +154,7 @@ describe Gitlab::ProjectSearchResults do let(:non_member) { create(:user) } let(:member) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } let!(:issue) { create(:issue, project: project, title: 'Issue 1') } let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) } @@ -226,7 +226,7 @@ describe Gitlab::ProjectSearchResults do describe 'notes search' do it 'lists notes' do - project = create(:empty_project, :public) + project = create(:project, :public) note = create(:note, project: project) results = described_class.new(user, project, note.note) @@ -235,7 +235,7 @@ describe Gitlab::ProjectSearchResults do end it "doesn't list issue notes when access is restricted" do - project = create(:empty_project, :public, :issues_private) + project = create(:project, :public, :issues_private) note = create(:note_on_issue, project: project) results = described_class.new(user, project, note.note) @@ -244,7 +244,7 @@ describe Gitlab::ProjectSearchResults do end it "doesn't list merge_request notes when access is restricted" do - project = create(:empty_project, :public, :merge_requests_private) + project = create(:project, :public, :merge_requests_private) note = create(:note_on_merge_request, project: project) results = described_class.new(user, project, note.note) diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb new file mode 100644 index 00000000000..12e75cdd5d0 --- /dev/null +++ b/spec/lib/gitlab/project_template_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe Gitlab::ProjectTemplate do + describe '.all' do + it 'returns a all templates' do + expected = [ + described_class.new('rails', 'Ruby on Rails') + ] + + expect(described_class.all).to be_an(Array) + expect(described_class.all).to eq(expected) + end + end + + describe '.find' do + subject { described_class.find(query) } + + context 'when there is a match' do + let(:query) { :rails } + + it { is_expected.to be_a(described_class) } + end + + context 'when there is no match' do + let(:query) { 'no-match' } + + it { is_expected.to be(nil) } + end + end + + describe 'instance methods' do + subject { described_class.new('phoenix', 'Phoenix Framework') } + + it { is_expected.to respond_to(:logo, :file, :archive_path) } + end + + describe 'validate all templates' do + set(:admin) { create(:admin) } + + described_class.all.each do |template| + it "#{template.name} has a valid archive" do + archive = template.archive_path + + expect(File.exist?(archive)).to be(true) + end + + context 'with valid parameters' do + it 'can be imported' do + params = { + template_name: template.name, + namespace_id: admin.namespace.id, + path: template.name + } + + project = Projects::CreateFromTemplateService.new(admin, params).execute + + expect(project).to be_valid + expect(project).to be_persisted + end + end + end + end +end diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb index e42e034f4fb..c7169717fc1 100644 --- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb @@ -1,19 +1,14 @@ require 'spec_helper' describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do - include Prometheus::MetricBuilders - - let(:client) { double('prometheus_client') } - let(:environment) { create(:environment, slug: 'environment-slug') } - let(:deployment) { create(:deployment, environment: environment) } - - subject(:query_result) { described_class.new(client).query(deployment.id) } - around do |example| Timecop.freeze(Time.local(2008, 9, 1, 12, 0, 0)) { example.run } end include_examples 'additional metrics query' do + let(:deployment) { create(:deployment, environment: environment) } + let(:query_params) { [deployment.id] } + it 'queries using specific time' do expect(client).to receive(:query_range).with(anything, start: (deployment.created_at - 30.minutes).to_f, diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb index e9fd66d45fe..5a88b23aa82 100644 --- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb @@ -1,20 +1,16 @@ require 'spec_helper' describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do - include Prometheus::MetricBuilders - - let(:client) { double('prometheus_client') } - let(:environment) { create(:environment, slug: 'environment-slug') } - - subject(:query_result) { described_class.new(client).query(environment.id) } - around do |example| Timecop.freeze { example.run } end include_examples 'additional metrics query' do + let(:query_params) { [environment.id] } + it 'queries using specific time' do expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f) + expect(query_result).not_to be_nil end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 1a0357534f2..476a3f1998d 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ReferenceExtractor do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.team << [project.creator, :developer] @@ -215,7 +215,7 @@ describe Gitlab::ReferenceExtractor do end context 'with a project with an underscore' do - let(:other_project) { create(:empty_project, path: 'test_project') } + let(:other_project) { create(:project, path: 'test_project') } let(:issue) { create(:issue, project: other_project) } before do diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index efea4f429bf..1a925a15e0c 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ::Gitlab::RepoPath do describe '.parse' do - set(:project) { create(:project) } + set(:project) { create(:project, :repository) } context 'a repository storage path' do it 'parses a full repository path' do @@ -65,7 +65,7 @@ describe ::Gitlab::RepoPath do end describe '.find_project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:redirect) { project.route.create_redirect('foo/bar/baz') } context 'when finding a project by its canonical path' do diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 2827a18515e..19710029224 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -109,7 +109,7 @@ describe Gitlab::Saml::User do end it 'does not throw an error' do - expect{ saml_user.save }.not_to raise_error + expect { saml_user.save }.not_to raise_error end end @@ -119,7 +119,7 @@ describe Gitlab::Saml::User do end it 'throws an error' do - expect{ saml_user.save }.to raise_error StandardError + expect { saml_user.save }.to raise_error StandardError end end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 31c3cd4d53c..4c5efbde69a 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::SearchResults do let(:user) { create(:user) } - let!(:project) { create(:empty_project, name: 'foo') } + let!(:project) { create(:project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } let!(:merge_request) do @@ -42,7 +42,7 @@ describe Gitlab::SearchResults do end it 'includes merge requests from source and target projects' do - forked_project = create(:empty_project, forked_from_project: project) + forked_project = create(:project, forked_from_project: project) merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') results = described_class.new(user, Project.where(id: forked_project.id), 'foo') @@ -52,17 +52,17 @@ describe Gitlab::SearchResults do end it 'does not list issues on private projects' do - private_project = create(:empty_project, :private) + private_project = create(:project, :private) issue = create(:issue, project: private_project, title: 'foo') expect(results.objects('issues')).not_to include issue end describe 'confidential issues' do - let(:project_1) { create(:empty_project, :internal) } - let(:project_2) { create(:empty_project, :internal) } - let(:project_3) { create(:empty_project, :internal) } - let(:project_4) { create(:empty_project, :internal) } + let(:project_1) { create(:project, :internal) } + let(:project_2) { create(:project, :internal) } + let(:project_3) { create(:project, :internal) } + let(:project_4) { create(:project, :internal) } let(:query) { 'issue' } let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) } let(:author) { create(:user) } diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index b90d8dede0f..2345874cf10 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -174,20 +174,94 @@ describe Gitlab::Shell do end describe '#fetch_remote' do + def fetch_remote(ssh_auth = nil) + gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage', ssh_auth: ssh_auth) + end + + def expect_popen(vars = {}) + popen_args = [ + projects_path, + 'fetch-remote', + 'current/storage', + 'project/path.git', + 'new/storage', + Gitlab.config.gitlab_shell.git_timeout.to_s + ] + + expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)) + end + + def build_ssh_auth(opts = {}) + defaults = { + ssh_import?: true, + ssh_key_auth?: false, + ssh_known_hosts: nil, + ssh_private_key: nil + } + + double(:ssh_auth, defaults.merge(opts)) + end + it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], - nil, popen_vars).and_return([nil, 0]) + expect_popen.and_return([nil, 0]) - expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true + expect(fetch_remote).to be_truthy end it 'raises an exception when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], - nil, popen_vars).and_return(["error", 1]) + expect_popen.and_return(["error", 1]) + + expect { fetch_remote }.to raise_error(Gitlab::Shell::Error, "error") + end + + context 'SSH auth' do + it 'passes the SSH key if specified' do + expect_popen('GITLAB_SHELL_SSH_KEY' => 'foo').and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass an empty SSH key' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass the key unless SSH key auth is to be used' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'passes the known_hosts data if specified' do + expect_popen('GITLAB_SHELL_KNOWN_HOSTS' => 'foo').and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass empty known_hosts data' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_known_hosts: '') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass known_hosts data unless SSH is to be used' do + expect_popen(popen_vars).and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo') - expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error") + expect(fetch_remote(ssh_auth)).to be_truthy + end end end diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index 88f73bf90cd..0173a45d480 100644 --- a/spec/lib/gitlab/slash_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::Command do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '#execute' do @@ -11,7 +11,7 @@ describe Gitlab::SlashCommands::Command do context 'when no command is available' do let(:params) { { text: 'issue show 1' } } - let(:project) { create(:empty_project, has_external_issue_tracker: true) } + let(:project) { create(:project, has_external_issue_tracker: true) } it 'displays 404 messages' do expect(subject[:response_type]).to be(:ephemeral) diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb index c3fb7d5adea..74b5ef4bb26 100644 --- a/spec/lib/gitlab/slash_commands/deploy_spec.rb +++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::Deploy do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:regex_match) { described_class.match('deploy staging to production') } diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb index 5dfb1b506bc..75ae58d0582 100644 --- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::IssueNew do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:regex_match) { described_class.match("issue create bird is the word") } diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index e5409fe2339..51f59216413 100644 --- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::SlashCommands::IssueSearch do describe '#execute' do let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue search find") } diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb index f67a17c7922..08c380ca8f1 100644 --- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::IssueShow do describe '#execute' do let(:issue) { create(:issue, project: project) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue show #{issue.iid}") } diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb index 7f81ebb47db..76e4bad88fd 100644 --- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::Presenters::IssueNew do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb index 7e57a0addcb..5a7ec0685fe 100644 --- a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::Presenters::IssueSearch do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:message) { subject[:text] } before do diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb index 2a6ed860737..8f607d7a9c9 100644 --- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SlashCommands::Presenters::IssueShow do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb index 5346881444d..baf8f6644bf 100644 --- a/spec/lib/gitlab/sql/union_spec.rb +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::SQL::Union do empty_relation = User.none union = described_class.new([empty_relation, relation_1, relation_2]) - expect{User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error + expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}") end end diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index bf45c8d16d6..6e0b1075a89 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -51,7 +51,7 @@ describe Gitlab::Template::IssueTemplate do end context 'when repo is bare or empty' do - let(:empty_project) { create(:empty_project) } + let(:empty_project) { create(:project) } before do empty_project.add_user(user, Gitlab::Access::MASTER) @@ -78,7 +78,7 @@ describe Gitlab::Template::IssueTemplate do end context "when repo is empty" do - let(:empty_project) { create(:empty_project) } + let(:empty_project) { create(:project) } before do empty_project.add_user(user, Gitlab::Access::MASTER) diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index 8479f92c8df..b952274cd24 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -51,7 +51,7 @@ describe Gitlab::Template::MergeRequestTemplate do end context 'when repo is bare or empty' do - let(:empty_project) { create(:empty_project) } + let(:empty_project) { create(:project) } before do empty_project.add_user(user, Gitlab::Access::MASTER) @@ -78,7 +78,7 @@ describe Gitlab::Template::MergeRequestTemplate do end context "when repo is empty" do - let(:empty_project) { create(:empty_project) } + let(:empty_project) { create(:project) } before do empty_project.add_user(user, Gitlab::Access::MASTER) diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb index 109559bb01c..4275e7b015b 100644 --- a/spec/lib/gitlab/uploads_transfer_spec.rb +++ b/spec/lib/gitlab/uploads_transfer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::UploadsTransfer do it 'leaves avatar uploads where they are' do - project_with_avatar = create(:empty_project, :with_avatar) + project_with_avatar = create(:project, :with_avatar) described_class.new.rename_namespace('project', 'project-renamed') diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 50422020823..b81749cf428 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(commit) - expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.path_with_namespace}/commit/#{commit.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.full_path}/commit/#{commit.id}" end end @@ -18,7 +18,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(issue) - expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}" + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/issues/#{issue.iid}" end end @@ -28,7 +28,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(merge_request) - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}" + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.full_path}/merge_requests/#{merge_request.iid}" end end @@ -39,7 +39,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.full_path}/commit/#{note.commit_id}#note_#{note.id}" end end @@ -49,7 +49,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.full_path}/commit/#{note.commit_id}#note_#{note.id}" end end @@ -60,7 +60,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/issues/#{issue.iid}#note_#{note.id}" end end @@ -71,7 +71,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.full_path}/merge_requests/#{merge_request.iid}#note_#{note.id}" end end @@ -82,7 +82,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.full_path}/merge_requests/#{merge_request.iid}#note_#{note.id}" end end @@ -93,7 +93,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(note) - expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}" + expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.full_path}/snippets/#{note.noteable_id}#note_#{note.id}" end end @@ -110,7 +110,7 @@ describe Gitlab::UrlBuilder do context 'on another object' do it 'returns a proper URL' do - project = build_stubbed(:empty_project) + project = build_stubbed(:project) expect { described_class.build(project) } .to raise_error(NotImplementedError, 'No URL builder defined for Project') diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index 5ebaf6c1507..cd97416bcc9 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::UserAccess do let(:access) { described_class.new(user, project: project) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } describe '#can_push_to_branch?' do diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb index e7e1a92ae54..c8a1e433d59 100644 --- a/spec/lib/gitlab/version_info_spec.rb +++ b/spec/lib/gitlab/version_info_spec.rb @@ -50,8 +50,8 @@ describe 'Gitlab::VersionInfo' do context 'unknown' do it { expect(@unknown).not_to be @v0_0_1 } it { expect(@unknown).not_to be Gitlab::VersionInfo.new } - it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) } - it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) } + it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) } + it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) } end context 'parse' do diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb index f2c152cdcd4..32a946ca034 100644 --- a/spec/lib/gitlab/view/presenter/base_spec.rb +++ b/spec/lib/gitlab/view/presenter/base_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::View::Presenter::Base do describe '#can?' do context 'user is not allowed' do it 'returns false' do - presenter = presenter_class.new(build_stubbed(:empty_project)) + presenter = presenter_class.new(build_stubbed(:project)) expect(presenter.can?(nil, :read_project)).to be_falsy end @@ -34,7 +34,7 @@ describe Gitlab::View::Presenter::Base do context 'user is allowed' do it 'returns true' do - presenter = presenter_class.new(build_stubbed(:empty_project, :public)) + presenter = presenter_class.new(build_stubbed(:project, :public)) expect(presenter.can?(nil, :read_project)).to be_truthy end @@ -42,9 +42,9 @@ describe Gitlab::View::Presenter::Base do context 'subject is overriden' do it 'returns true' do - presenter = presenter_class.new(build_stubbed(:empty_project, :public)) + presenter = presenter_class.new(build_stubbed(:project, :public)) - expect(presenter.can?(nil, :read_project, build_stubbed(:empty_project))).to be_falsy + expect(presenter.can?(nil, :read_project, build_stubbed(:project))).to be_falsy end end end diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb index e7022bd06f8..d6edc964844 100644 --- a/spec/lib/json_web_token/rsa_token_spec.rb +++ b/spec/lib/json_web_token/rsa_token_spec.rb @@ -27,7 +27,7 @@ describe JSONWebToken::RSAToken do subject { JWT.decode(rsa_encoded, rsa_key) } - it { expect{subject}.not_to raise_error } + it { expect {subject}.not_to raise_error } it { expect(subject.first).to include('key' => 'value') } it do expect(subject.second).to eq( @@ -41,7 +41,7 @@ describe JSONWebToken::RSAToken do let(:new_key) { OpenSSL::PKey::RSA.generate(512) } subject { JWT.decode(rsa_encoded, new_key) } - it { expect{subject}.to raise_error(JWT::DecodeError) } + it { expect {subject}.to raise_error(JWT::DecodeError) } end end end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index be3908e8f6a..3db19d06305 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -20,9 +20,10 @@ describe Mattermost::Session, type: :request do describe '#with session' do let(:location) { 'http://location.tld' } + let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'} let!(:stub) do WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login") - .to_return(headers: { 'location' => location }, status: 307) + .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 307) end context 'without oauth uri' do @@ -34,9 +35,9 @@ describe Mattermost::Session, type: :request do context 'with oauth_uri' do let!(:doorkeeper) do Doorkeeper::Application.create( - name: "GitLab Mattermost", + name: 'GitLab Mattermost', redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete", - scopes: "") + scopes: '') end context 'without token_uri' do diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb index 0c15ba22bf2..8b0c7254b5e 100644 --- a/spec/lib/repository_cache_spec.rb +++ b/spec/lib/repository_cache_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryCache do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:backend) { double('backend').as_null_object } let(:cache) { described_class.new('example', project.id, backend) } diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb index 025ea2673b4..4de5da984ba 100644 --- a/spec/lib/system_check/simple_executor_spec.rb +++ b/spec/lib/system_check/simple_executor_spec.rb @@ -240,7 +240,7 @@ describe SystemCheck::SimpleExecutor do context 'when there is an exception' do it 'rescues the exception' do - expect{ subject.run_check(BugousCheck) }.not_to raise_exception + expect { subject.run_check(BugousCheck) }.not_to raise_exception end end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 683e893968b..e36d7a1800c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -348,7 +348,7 @@ describe Notify do end describe 'project was moved' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } @@ -366,7 +366,7 @@ describe Notify do describe 'project access requested' do context 'for a project in a user namespace' do let(:project) do - create(:empty_project, :public, :access_requestable) do |project| + create(:project, :public, :access_requestable) do |project| project.team << [project.owner, :master, project.owner] end end @@ -397,7 +397,7 @@ describe Notify do context 'for a project in a group' do let(:group_owner) { create(:user) } let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } - let(:project) { create(:empty_project, :public, :access_requestable, namespace: group) } + let(:project) { create(:project, :public, :access_requestable, namespace: group) } let(:user) { create(:user) } let(:project_member) do project.request_access(user) @@ -423,7 +423,7 @@ describe Notify do end describe 'project access denied' do - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:user) { create(:user) } let(:project_member) do project.request_access(user) @@ -444,7 +444,7 @@ describe Notify do describe 'project access changed' do let(:owner) { create(:user, name: "Chang O'Keefe") } - let(:project) { create(:empty_project, :public, :access_requestable, namespace: owner.namespace) } + let(:project) { create(:project, :public, :access_requestable, namespace: owner.namespace) } let(:user) { create(:user) } let(:project_member) { create(:project_member, project: project, user: user) } subject { described_class.member_access_granted_email('project', project_member.id) } @@ -474,7 +474,7 @@ describe Notify do end describe 'project invitation' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:project_member) { invite_to_project(project, inviter: master) } @@ -494,7 +494,7 @@ describe Notify do end describe 'project invitation accepted' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:invited_user) { create(:user, name: 'invited user') } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:project_member) do @@ -519,7 +519,7 @@ describe Notify do end describe 'project invitation declined' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:project_member) do invitee = invite_to_project(project, inviter: master) @@ -1242,7 +1242,7 @@ describe Notify do end describe 'HTML emails setting' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb index 65bea662b02..862907c5d01 100644 --- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb +++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb @@ -4,7 +4,7 @@ require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_ describe AddHeadPipelineForEachMergeRequest, :truncate do let(:migration) { described_class.new } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } let!(:other_project) { forked_project_link.forked_to_project } diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb new file mode 100644 index 00000000000..597d8eab51c --- /dev/null +++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb') + +describe CalculateConvDevIndexPercentages, truncate: true do + let(:migration) { described_class.new } + let!(:conv_dev_index) do + create(:conversational_development_index_metric, + leader_notes: 0, + instance_milestones: 0, + percentage_issues: 0, + percentage_notes: 0, + percentage_milestones: 0, + percentage_boards: 0, + percentage_merge_requests: 0, + percentage_ci_pipelines: 0, + percentage_environments: 0, + percentage_deployments: 0, + percentage_projects_prometheus_active: 0, + percentage_service_desk_issues: 0) + end + + describe '#up' do + it 'calculates percentages correctly' do + migration.up + conv_dev_index.reload + + expect(conv_dev_index.percentage_issues).to be_within(0.1).of(13.3) + expect(conv_dev_index.percentage_notes).to be_zero # leader 0 + expect(conv_dev_index.percentage_milestones).to be_zero # instance 0 + expect(conv_dev_index.percentage_boards).to be_within(0.1).of(62.4) + expect(conv_dev_index.percentage_merge_requests).to eq(50.0) + expect(conv_dev_index.percentage_ci_pipelines).to be_within(0.1).of(19.3) + expect(conv_dev_index.percentage_environments).to be_within(0.1).of(66.7) + expect(conv_dev_index.percentage_deployments).to be_within(0.1).of(64.2) + expect(conv_dev_index.percentage_projects_prometheus_active).to be_within(0.1).of(98.2) + expect(conv_dev_index.percentage_service_desk_issues).to be_within(0.1).of(84.0) + end + end +end diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb index 49e750a3f4d..12cac1d033d 100644 --- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb +++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb @@ -4,15 +4,15 @@ require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespacel describe CleanupNamespacelessPendingDeleteProjects do before do # Stub after_save callbacks that will fail when Project has no namespace - allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil) + allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil) allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil) end describe '#up' do it 'only cleans up pending delete projects' do - create(:empty_project) - create(:empty_project, pending_delete: true) - project = build(:empty_project, pending_delete: true, namespace_id: nil) + create(:project) + create(:project, pending_delete: true) + project = build(:project, pending_delete: true, namespace_id: nil) project.save(validate: false) expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]]) @@ -21,8 +21,8 @@ describe CleanupNamespacelessPendingDeleteProjects do end it 'does nothing when no pending delete projects without namespace found' do - create(:empty_project) - create(:empty_project, pending_delete: true) + create(:project) + create(:project, pending_delete: true) expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async) diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb index 148290b0e7d..5ef10b92a3a 100644 --- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb +++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb @@ -37,7 +37,7 @@ describe FixWronglyRenamedRoutes, truncate: true do describe '#routes_in_namespace_query' do it 'includes only the required routes' do namespace = create(:group, path: 'hello') - project = create(:empty_project, namespace: namespace) + project = create(:project, namespace: namespace) _other_namespace = create(:group, path: 'hello0') result = Route.where(subject.routes_in_namespace_query('hello')) @@ -48,7 +48,7 @@ describe FixWronglyRenamedRoutes, truncate: true do describe '#up' do let(:broken_project) do - project = create(:empty_project, namespace: broken_namespace, path: 'broken-project') + project = create(:project, namespace: broken_namespace, path: 'broken-project') project.route.update_attribute(:path, 'api0is/broken-project') project end diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb index 50f4bbda001..cfe1ca481b2 100644 --- a/spec/migrations/migrate_old_artifacts_spec.rb +++ b/spec/migrations/migrate_old_artifacts_spec.rb @@ -16,9 +16,9 @@ describe MigrateOldArtifacts do end context 'with migratable data' do - let(:project1) { create(:empty_project, ci_id: 2) } - let(:project2) { create(:empty_project, ci_id: 3) } - let(:project3) { create(:empty_project) } + let(:project1) { create(:project, ci_id: 2) } + let(:project2) { create(:project, ci_id: 3) } + let(:project3) { create(:project) } let(:pipeline1) { create(:ci_empty_pipeline, project: project1) } let(:pipeline2) { create(:ci_empty_pipeline, project: project2) } diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index 5b633dd349b..e5793a3c0ee 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -6,7 +6,7 @@ require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_ describe MigrateProcessCommitWorkerJobs do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:commit) { project.commit.raw.raw_commit } + let(:commit) { project.commit.raw.rugged_commit } describe 'Project' do describe 'find_including_path' do @@ -20,7 +20,7 @@ describe MigrateProcessCommitWorkerJobs do .find_including_path(project.id) expect(migration_project[:path_with_namespace]) - .to eq(project.path_with_namespace) + .to eq(project.full_path) end end diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb index 4bd8d4ac0d1..ae3a4cb9b29 100644 --- a/spec/migrations/rename_more_reserved_project_names_spec.rb +++ b/spec/migrations/rename_more_reserved_project_names_spec.rb @@ -8,7 +8,7 @@ require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserv # around this we use the TRUNCATE cleaning strategy. describe RenameMoreReservedProjectNames, truncate: true do let(:migration) { described_class.new } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } before do project.path = 'artifacts' diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb index 05e021c2e32..462f4c08d63 100644 --- a/spec/migrations/rename_reserved_project_names_spec.rb +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -8,7 +8,7 @@ require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_pr # around this we use the TRUNCATE cleaning strategy. describe RenameReservedProjectNames, truncate: true do let(:migration) { described_class.new } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } before do project.path = 'projects' diff --git a/spec/migrations/rename_system_namespaces_spec.rb b/spec/migrations/rename_system_namespaces_spec.rb index 626a6005838..747694cbe33 100644 --- a/spec/migrations/rename_system_namespaces_spec.rb +++ b/spec/migrations/rename_system_namespaces_spec.rb @@ -58,7 +58,7 @@ describe RenameSystemNamespaces, truncate: true do end it "renames the route for projects of the namespace" do - project = build(:project, path: "project-path", namespace: system_namespace) + project = build(:project, :repository, path: "project-path", namespace: system_namespace) save_invalid_routable(project) migration.up @@ -68,7 +68,7 @@ describe RenameSystemNamespaces, truncate: true do it "doesn't touch routes of namespaces that look like system" do namespace = create(:group, path: 'systemlookalike') - project = create(:project, namespace: namespace, path: 'the-project') + project = create(:project, :repository, namespace: namespace, path: 'the-project') migration.up @@ -77,7 +77,7 @@ describe RenameSystemNamespaces, truncate: true do end it "moves the the repository for a project in the namespace" do - project = build(:project, namespace: system_namespace, path: "system-project") + project = build(:project, :repository, namespace: system_namespace, path: "system-project") save_invalid_routable(project) TestEnv.copy_repo(project, bare_repo: TestEnv.factory_repo_path_bare, @@ -105,7 +105,7 @@ describe RenameSystemNamespaces, truncate: true do describe "clears the markdown cache for projects in the system namespace" do let!(:project) do - project = build(:project, namespace: system_namespace) + project = build(:project, :repository, namespace: system_namespace) save_invalid_routable(project) project end @@ -161,7 +161,7 @@ describe RenameSystemNamespaces, truncate: true do it "updates the route of the project correctly" do subgroup = build(:group, path: "subgroup", parent: system_namespace) save_invalid_routable(subgroup) - project = build(:project, path: "system0", namespace: subgroup) + project = build(:project, :repository, path: "system0", namespace: subgroup) save_invalid_routable(project) migration.up @@ -174,7 +174,7 @@ describe RenameSystemNamespaces, truncate: true do describe "#move_repositories" do let(:namespace) { create(:group, name: "hello-group") } it "moves a project for a namespace" do - create(:project, namespace: namespace, path: "hello-project") + create(:project, :repository, namespace: namespace, path: "hello-project") expected_path = File.join(TestEnv.repos_path, "bye-group", "hello-project.git") migration.move_repositories(namespace, "hello-group", "bye-group") @@ -184,7 +184,7 @@ describe RenameSystemNamespaces, truncate: true do it "moves a namespace in a subdirectory correctly" do child_namespace = create(:group, name: "sub-group", parent: namespace) - create(:project, namespace: child_namespace, path: "hello-project") + create(:project, :repository, namespace: child_namespace, path: "hello-project") expected_path = File.join(TestEnv.repos_path, "hello-group", "renamed-sub-group", "hello-project.git") @@ -195,7 +195,7 @@ describe RenameSystemNamespaces, truncate: true do it "moves a parent namespace with subdirectories" do child_namespace = create(:group, name: "sub-group", parent: namespace) - create(:project, namespace: child_namespace, path: "hello-project") + create(:project, :repository, namespace: child_namespace, path: "hello-project") expected_path = File.join(TestEnv.repos_path, "renamed-group", "sub-group", "hello-project.git") migration.move_repositories(child_namespace, "hello-group", "renamed-group") diff --git a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb new file mode 100644 index 00000000000..f95bd6e3511 --- /dev/null +++ b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170703130158_schedule_merge_request_diff_migrations') + +describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do + matcher :be_scheduled_migration do |time, *expected| + match do |migration| + BackgroundMigrationWorker.jobs.any? do |job| + job['args'] == [migration, expected] && + job['at'].to_i == time.to_i + end + end + + failure_message do |migration| + "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!" + end + end + + let(:merge_request_diffs) { table(:merge_request_diffs) } + let(:merge_requests) { table(:merge_requests) } + let(:projects) { table(:projects) } + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + + projects.create!(id: 1, name: 'gitlab', path: 'gitlab') + + merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master') + + merge_request_diffs.create!(id: 1, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: nil) + merge_request_diffs.create!(id: 2, merge_request_id: 1, st_commits: nil, st_diffs: YAML.dump([])) + merge_request_diffs.create!(id: 3, merge_request_id: 1, st_commits: nil, st_diffs: nil) + merge_request_diffs.create!(id: 4, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: YAML.dump([])) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes.from_now, 1, 1) + expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes.from_now, 2, 2) + expect(described_class::MIGRATION).to be_scheduled_migration(15.minutes.from_now, 4, 4) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end + + it 'schedules background migrations' do + Sidekiq::Testing.inline! do + non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' + + expect(merge_request_diffs.where(non_empty).count).to eq 3 + + migrate! + + expect(merge_request_diffs.where(non_empty).count).to eq 0 + end + end +end diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb index 42109fd0743..6f7a730edff 100644 --- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb +++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb @@ -44,7 +44,7 @@ describe TurnNestedGroupsIntoRegularGroupsForMysql do end it 'renames projects of the nested group' do - expect(updated_project.path_with_namespace) + expect(updated_project.full_path) .to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}") end diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb index 7df44515424..11412005b72 100644 --- a/spec/migrations/update_upload_paths_to_system_spec.rb +++ b/spec/migrations/update_upload_paths_to_system_spec.rb @@ -11,9 +11,9 @@ describe UpdateUploadPathsToSystem do describe "#uploads_to_switch_to_new_path" do it "contains only uploads with the old path for the correct models" do _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg") - _upload_with_system_path = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg") - _upload_with_other_path = create(:upload, model: create(:empty_project), path: "thelongsecretforafileupload/avatar.jpg") - old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg") + _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg") + _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg") + old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg") group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg") expect(Upload.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload) @@ -23,9 +23,9 @@ describe UpdateUploadPathsToSystem do describe "#uploads_to_switch_to_old_path" do it "contains only uploads with the new path for the correct models" do _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg") - upload_with_system_path = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg") - _upload_with_other_path = create(:upload, model: create(:empty_project), path: "thelongsecretforafileupload/avatar.jpg") - _old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg") + upload_with_system_path = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg") + _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg") + _old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg") expect(Upload.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path) end @@ -33,7 +33,7 @@ describe UpdateUploadPathsToSystem do describe "#up", truncate: true do it "updates old upload records to the new path" do - old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg") + old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg") migration.up @@ -43,7 +43,7 @@ describe UpdateUploadPathsToSystem do describe "#down", truncate: true do it "updates the new system patsh to the old paths" do - new_upload = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg") + new_upload = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg") migration.down diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index aa019288700..71aa51e1857 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -8,7 +8,7 @@ describe Ability do end describe '.can_edit_note?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:note) { create(:note_on_issue, project: project) } context 'using an anonymous user' do @@ -66,7 +66,7 @@ describe Ability do describe '.users_that_can_read_project' do context 'using a public project' do it 'returns all the users' do - project = create(:empty_project, :public) + project = create(:project, :public) user = build(:user) expect(described_class.users_that_can_read_project([user], project)) @@ -75,7 +75,7 @@ describe Ability do end context 'using an internal project' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -126,7 +126,7 @@ describe Ability do end context 'using a private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -253,7 +253,7 @@ describe Ability do end describe '.project_disabled_features_rules' do - let(:project) { create(:empty_project, :wiki_disabled) } + let(:project) { create(:project, :wiki_disabled) } subject { described_class.policy_for(project.owner, project) } diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index e1193e0d19a..47342f98283 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' describe Blob do include FakeBlobHelpers - let(:project) { build(:empty_project, lfs_enabled: true) } + let(:project) { build(:project, lfs_enabled: true) } before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb index e3640097dff..7ba28f72215 100644 --- a/spec/models/blob_viewer/base_spec.rb +++ b/spec/models/blob_viewer/base_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::Base do include FakeBlobHelpers - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:viewer_class) do Class.new(described_class) do diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb index 82f6f7e5046..85b0d9668a0 100644 --- a/spec/models/blob_viewer/composer_json_spec.rb +++ b/spec/models/blob_viewer/composer_json_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::ComposerJson do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-SPEC.strip_heredoc { diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb index 14cc5f3c0fd..d8c4490637f 100644 --- a/spec/models/blob_viewer/gemspec_spec.rb +++ b/spec/models/blob_viewer/gemspec_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::Gemspec do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-SPEC.strip_heredoc Gem::Specification.new do |s| diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb index 7a4f9866375..bed364a8c14 100644 --- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb +++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::GitlabCiYml do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) } subject { described_class.new(blob) } diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb index 96fb1b08c99..0f8330e91c1 100644 --- a/spec/models/blob_viewer/package_json_spec.rb +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::PackageJson do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-SPEC.strip_heredoc { diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb index f510077a87b..9a23877b23f 100644 --- a/spec/models/blob_viewer/podspec_json_spec.rb +++ b/spec/models/blob_viewer/podspec_json_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::PodspecJson do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-SPEC.strip_heredoc { diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb index 7c38083550c..02d06ea24d6 100644 --- a/spec/models/blob_viewer/podspec_spec.rb +++ b/spec/models/blob_viewer/podspec_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::Podspec do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-SPEC.strip_heredoc Pod::Spec.new do |spec| diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb index 115731b4970..c13662427b0 100644 --- a/spec/models/blob_viewer/route_map_spec.rb +++ b/spec/models/blob_viewer/route_map_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::RouteMap do include FakeBlobHelpers - let(:project) { build(:project) } + let(:project) { build_stubbed(:project) } let(:data) do <<-MAP.strip_heredoc # Team data diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb index 2639eec9e84..63790486200 100644 --- a/spec/models/blob_viewer/server_side_spec.rb +++ b/spec/models/blob_viewer/server_side_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobViewer::ServerSide do include FakeBlobHelpers - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:viewer_class) do Class.new(BlobViewer::Base) do @@ -25,7 +25,7 @@ describe BlobViewer::ServerSide do describe '#render_error' do context 'when the blob is stored externally' do - let(:project) { build(:empty_project, lfs_enabled: true) } + let(:project) { build(:project, lfs_enabled: true) } let(:blob) { fake_blob(path: 'file.pdf', lfs: true) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b0efa689a07..ac75c6501ee 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,10 +1,8 @@ require 'spec_helper' -describe Ci::Pipeline do - include EmailHelpers - +describe Ci::Pipeline, :mailer do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_empty_pipeline, status: :created, project: project) @@ -93,7 +91,7 @@ describe Ci::Pipeline do end describe "coverage" do - let(:project) { create(:empty_project, build_coverage_regex: "/.*/") } + let(:project) { create(:project, build_coverage_regex: "/.*/") } let(:pipeline) { create(:ci_empty_pipeline, project: project) } it "calculates average when there are two builds with coverage" do @@ -1147,7 +1145,7 @@ describe Ci::Pipeline do end describe "#merge_requests" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do @@ -1172,7 +1170,7 @@ describe Ci::Pipeline do end describe "#all_merge_requests" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') } it "returns all merge requests having the same source branch" do @@ -1248,8 +1246,6 @@ describe Ci::Pipeline do pipeline.user.global_notification_setting .update(level: 'custom', failed_pipeline: true, success_pipeline: true) - reset_delivered_emails! - perform_enqueued_jobs do pipeline.enqueue pipeline.run diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 8d12a9c09ca..48f878bbee6 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -37,7 +37,7 @@ describe Ci::Runner do end describe '#assign_to' do - let!(:project) { FactoryGirl.create :empty_project } + let!(:project) { FactoryGirl.create :project } let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) } before do @@ -339,8 +339,8 @@ describe Ci::Runner do describe '.assignable_for' do let(:runner) { create(:ci_runner) } - let(:project) { create(:empty_project) } - let(:another_project) { create(:empty_project) } + let(:project) { create(:project) } + let(:another_project) { create(:project) } before do project.runners << runner @@ -400,8 +400,8 @@ describe Ci::Runner do describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do runner = FactoryGirl.create(:ci_runner) - project = FactoryGirl.create(:empty_project) - project1 = FactoryGirl.create(:empty_project) + project = FactoryGirl.create(:project) + project1 = FactoryGirl.create(:project) project.runners << runner project1.runners << runner @@ -410,7 +410,7 @@ describe Ci::Runner do it "returns true" do runner = FactoryGirl.create(:ci_runner) - project = FactoryGirl.create(:empty_project) + project = FactoryGirl.create(:project) project.runners << runner expect(runner.belongs_to_one_project?).to be_truthy diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index de51f4879fd..bd9c837402f 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::Trigger do - let(:project) { create :empty_project } + let(:project) { create :project } describe 'associations' do it { is_expected.to belong_to(:project) } diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 07e10b44938..38829773599 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -45,7 +45,7 @@ describe CommitRange do end describe '#to_reference' do - let(:cross) { create(:empty_project, namespace: project.namespace) } + let(:cross) { create(:project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" @@ -61,7 +61,7 @@ describe CommitRange do end describe '#reference_link_text' do - let(:cross) { create(:empty_project, namespace: project.namespace) } + let(:cross) { create(:project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 2285c338599..c18c635d811 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -33,7 +33,6 @@ describe Commit do describe '#to_reference' do let(:project) { create(:project, :repository, path: 'sample-project') } - let(:commit) { project.commit } it 'returns a String reference to the object' do expect(commit.to_reference).to eq commit.id @@ -47,7 +46,6 @@ describe Commit do describe '#reference_link_text' do let(:project) { create(:project, :repository, path: 'sample-project') } - let(:commit) { project.commit } it 'returns a String reference to the object' do expect(commit.reference_link_text).to eq commit.short_id @@ -151,7 +149,7 @@ eos describe '#closes_issues' do let(:issue) { create :issue, project: project } - let(:other_project) { create(:empty_project, :public) } + let(:other_project) { create(:project, :public) } let(:other_issue) { create :issue, project: other_project } let(:commiter) { create :user } @@ -161,7 +159,7 @@ eos end it 'detects issues that this commit is marked as closing' do - ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" + ext_ref = "#{other_project.full_path}##{other_issue.iid}" allow(commit).to receive_messages( safe_message: "Fixes ##{issue.iid} and #{ext_ref}", @@ -191,7 +189,7 @@ eos it { expect(data).to be_a(Hash) } it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } - it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') } + it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46Z') } it { expect(data[:added]).to eq(["bar/branch-test.txt"]) } it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 6fb4794ea5f..8c4a366ef8f 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -425,7 +425,7 @@ describe CommitStatus do end it "raise exception when trying to update" do - expect{ commit_status.save }.to raise_error(ActiveRecord::StaleObjectError) + expect { commit_status.save }.to raise_error(ActiveRecord::StaleObjectError) end end diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb index 97b7e48bb3c..04d6cfa2c02 100644 --- a/spec/models/concerns/access_requestable_spec.rb +++ b/spec/models/concerns/access_requestable_spec.rb @@ -24,14 +24,14 @@ describe AccessRequestable do describe 'Project' do describe '#request_access' do - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:user) { create(:user) } it { expect(project.request_access(user)).to be_a(ProjectMember) } end describe '#access_requested?' do - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:user) { create(:user) } before do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 505039c9d88..0137f71be8f 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -155,7 +155,7 @@ describe Issuable do end describe "#sort" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context "by milestone due date" do # Correct order is: @@ -296,7 +296,7 @@ describe Issuable do end describe '#labels_array' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:bug) { create(:label, project: project, title: 'bug') } let(:issue) { create(:issue, project: project) } @@ -310,7 +310,7 @@ describe Issuable do end describe '#user_notes_count' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } @@ -340,7 +340,7 @@ describe Issuable do end describe '.order_due_date_and_labels_priority' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } def create_issue(milestone, labels) create(:labeled_issue, milestone: milestone, labels: labels, project: project) @@ -394,7 +394,7 @@ describe Issuable do end describe ".with_label" do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:bug) { create(:label, project: project, title: 'bug') } let(:feature) { create(:label, project: project, title: 'feature') } let(:enhancement) { create(:label, project: project, title: 'enhancement') } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 1ad811736af..8b545aec7f5 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -13,7 +13,7 @@ describe Mentionable do end describe 'references' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:mentionable) { Example.new } it 'excludes JIRA references' do @@ -48,10 +48,10 @@ describe Issue, "Mentionable" do describe '#referenced_mentionables' do context 'with an issue on a private project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let(:public_issue) { create(:issue, project: project) } - let(:private_project) { create(:empty_project, :private) } + let(:private_project) { create(:project, :private) } let(:private_issue) { create(:issue, project: private_project) } let(:user) { create(:user) } @@ -102,7 +102,7 @@ describe Issue, "Mentionable" do end describe '#create_new_cross_references!' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:author) { create(:author) } let(:issues) { create_list(:issue, 2, project: project, author: author) } @@ -204,7 +204,7 @@ describe Commit, 'Mentionable' do end context 'with external issue tracker' do - let(:project) { create(:jira_project) } + let(:project) { create(:jira_project, :repository) } it 'is true if external issues referenced' do allow(commit.raw).to receive(:message).and_return 'JIRA-123' diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index cefe7fb6fea..66353935427 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -7,7 +7,7 @@ describe Milestone, 'Milestoneish' do let(:member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } let!(:issue) { create(:issue, project: project, milestone: milestone) } let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 6cf5877424d..9041690023f 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeaturesCompatibility do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:features) { %w(issues wiki builds merge_requests snippets) } # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb index 494e6f1b6f6..729056b6abc 100644 --- a/spec/models/concerns/relative_positioning_spec.rb +++ b/spec/models/concerns/relative_positioning_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RelativePositioning do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:issue1) { create(:issue, project: project) } let(:new_issue) { create(:issue, project: project) } diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb index 53eaa6f8461..d00faa4f8be 100644 --- a/spec/models/concerns/resolvable_note_spec.rb +++ b/spec/models/concerns/resolvable_note_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Note, ResolvableNote do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } subject { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) } diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 36aedd2f701..b463d12e448 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -156,13 +156,13 @@ end describe Project, 'Routable' do describe '#full_path' do - let(:project) { build_stubbed(:empty_project) } + let(:project) { build_stubbed(:project) } it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" } end describe '#full_name' do - let(:project) { build_stubbed(:empty_project) } + let(:project) { build_stubbed(:project) } it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" } end diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb index 58f5c164116..28ff8158e0e 100644 --- a/spec/models/concerns/subscribable_spec.rb +++ b/spec/models/concerns/subscribable_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Subscribable, 'Subscribable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:resource) { create(:issue, project: project) } let(:user_1) { create(:user) } diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index eff41d85972..bae88cb1d24 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ContainerRepository do let(:group) { create(:group, name: 'group') } - let(:project) { create(:project, path: 'test', group: group) } + let(:project) { create(:project, :repository, path: 'test', group: group) } let(:repository) do create(:container_repository, name: 'my_image', project: project) @@ -41,7 +41,7 @@ describe ContainerRepository do end context 'when path contains uppercase letters' do - let(:project) { create(:project, path: 'MY_PROJECT', group: group) } + let(:project) { create(:project, :repository, path: 'MY_PROJECT', group: group) } it 'returns a full path without capital letters' do expect(repository.path).to eq('group/my_project/my_image') diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb new file mode 100644 index 00000000000..b3193619503 --- /dev/null +++ b/spec/models/conversational_development_index/metric_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe ConversationalDevelopmentIndex::Metric do + let(:conv_dev_index) { create(:conversational_development_index_metric) } + + describe '#percentage_score' do + it 'returns stored percentage score' do + expect(conv_dev_index.percentage_score('issues')).to eq(13.331) + end + end +end diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 2aece75b817..3d7283e2164 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe DeployKey do - include EmailHelpers - +describe DeployKey, :mailer do describe "Associations" do it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:projects) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index f10b65ba9d8..0345fefb254 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -12,7 +12,7 @@ describe DeployKeysProject do end describe "Destroying" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { create(:deploy_keys_project, project: project) } let(:deploy_key) { subject.deploy_key } @@ -39,7 +39,7 @@ describe DeployKeysProject do end context "when the deploy key is used by more than one project" do - let!(:other_project) { create(:empty_project) } + let!(:other_project) { create(:project) } before do other_project.deploy_keys << deploy_key diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 6447095078b..c5708e70ef9 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -91,7 +91,7 @@ describe Deployment do end describe '#additional_metrics' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:deployment) { create(:deployment, project: project) } subject { deployment.additional_metrics } diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index 2704698f6c9..fa02434b0fd 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -5,7 +5,7 @@ describe DiffDiscussion do subject { described_class.new([diff_note]) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ebf2c070116..ea8512a5eae 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Environment do - set(:project) { create(:empty_project) } + set(:project) { create(:project) } subject(:environment) { create(:environment, project: project) } it { is_expected.to belong_to(:project) } @@ -21,7 +21,7 @@ describe Environment do it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) } describe '.order_by_last_deployed_at' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:environment1) { create(:environment, project: project) } let!(:environment2) { create(:environment, project: project) } let!(:environment3) { create(:environment, project: project) } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 4a4b84c9566..d86bf1a90a9 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -15,7 +15,7 @@ describe Event do end describe 'Callbacks' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe 'after_create :reset_project_activity' do it 'calls the reset_project_activity method' do @@ -53,7 +53,7 @@ describe Event do end describe "Push event" do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:user) { project.owner } let(:event) { create_push_event(project, user) } @@ -111,7 +111,7 @@ describe Event do end describe '#visible_to_user?' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:non_member) { create(:user) } let(:member) { create(:user) } let(:guest) { create(:user) } @@ -143,7 +143,7 @@ describe Event do end context 'private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it do aggregate_failures do @@ -213,7 +213,7 @@ describe Event do end context 'merge request diff note event' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } @@ -228,7 +228,7 @@ describe Event do end context 'private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it do expect(event.visible_to_user?(non_member)).to eq false @@ -260,7 +260,7 @@ describe Event do end describe '#reset_project_activity' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when a project was updated less than 1 hour ago' do it 'does not update the project' do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 38fbdd2536a..7dbeb4d2e74 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -41,7 +41,7 @@ describe ForkedProjectLink, "add link on fork" do end describe '#forked?' do - let(:project_to) { create(:project, forked_project_link: forked_project_link) } + let(:project_to) { create(:project, :repository, forked_project_link: forked_project_link) } let(:forked_project_link) { create(:forked_project_link) } before do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index aedc74deb78..7f1909710d8 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe GenericCommitStatus do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:external_url) { 'http://example.gitlab.com/status' } diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index 5584a1a5a31..ab58f5c5021 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -4,9 +4,9 @@ describe GlobalMilestone do let(:user) { create(:user) } let(:user2) { create(:user) } let(:group) { create(:group) } - let(:project1) { create(:empty_project, group: group) } - let(:project2) { create(:empty_project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:empty_project, path: 'cookbook-gitlab', group: group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } describe '.build_collection' do let(:milestone1_due_date) { 2.weeks.from_now.to_date } diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 59c074199db..e48f20bf53b 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -114,9 +114,7 @@ describe GpgKey do end end - describe 'notification' do - include EmailHelpers - + describe 'notification', :mailer do let(:user) { create(:user) } it 'sends a notification' do diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index 9a9b1900aa5..c58fd46762a 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -16,7 +16,7 @@ RSpec.describe GpgSignature do describe '#commit' do it 'fetches the commit through the project' do commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' - project = create :project + project = create :project, :repository commit = create :commit, project: project gpg_signature = create :gpg_signature, commit_sha: commit_sha diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index f7828059295..d0fc1eaa3ec 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -39,8 +39,8 @@ describe GroupLabel do context 'cross-project' do let(:namespace) { build_stubbed(:namespace) } - let(:source_project) { build_stubbed(:empty_project, name: 'project-1', namespace: namespace) } - let(:target_project) { build_stubbed(:empty_project, name: 'project-2', namespace: namespace) } + let(:source_project) { build_stubbed(:project, name: 'project-1', namespace: namespace) } + let(:target_project) { build_stubbed(:project, name: 'project-2', namespace: namespace) } it 'returns a String reference to the object' do expect(label.to_reference(source_project, target_project: target_project)).to eq %(project-1~#{label.id}) diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index ac76c927c39..b60676afc91 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GroupMilestone do let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } let(:project_milestone) do create(:milestone, title: "Milestone v1.2", project: project) end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 112bd605a64..c5bfae47606 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -425,7 +425,7 @@ describe Group do end describe '#secret_variables_for' do - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } let!(:secret_variable) do create(:ci_group_variable, value: 'secret', group: group) diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index 0e9b94aac97..2afdd6751a4 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Guest do - let(:public_project) { build_stubbed(:empty_project, :public) } - let(:private_project) { build_stubbed(:empty_project, :private) } - let(:internal_project) { build_stubbed(:empty_project, :internal) } + let(:public_project) { build_stubbed(:project, :public) } + let(:private_project) { build_stubbed(:project, :private) } + let(:internal_project) { build_stubbed(:project, :internal) } describe '.can_pull?' do context 'when project is private' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index eadc232a989..431e3db9f00 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -16,7 +16,7 @@ describe SystemHook do describe "execute" do let(:system_hook) { create(:system_hook) } let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let(:group) { create(:group) } let(:params) do { name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: 'mydummypass' } diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 6ceff7d24d4..1bf0ecb98ad 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issue::Metrics do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { create(:issue, project: project) } diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index 04d23d4c4fd..34d98a3c975 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe IssueCollection do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } let(:collection) { described_class.new([issue1, issue2]) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index d72790eefe5..6d825ba68d1 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -24,7 +24,7 @@ describe Issue do end describe '#order_by_position_and_priority' do - let(:project) { create :empty_project } + let(:project) { create :project } let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let!(:issue1) { create(:labeled_issue, project: project, labels: [p1]) } @@ -74,7 +74,7 @@ describe Issue do describe '#to_reference' do let(:namespace) { build(:namespace, path: 'sample-namespace') } - let(:project) { build(:empty_project, name: 'sample-project', namespace: namespace) } + let(:project) { build(:project, name: 'sample-project', namespace: namespace) } let(:issue) { build(:issue, iid: 1, project: project) } let(:group) { create(:group, name: 'Group', path: 'sample-group') } @@ -99,7 +99,7 @@ describe Issue do end context 'when cross namespace project argument' do - let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + let(:another_namespace_project) { create(:project, name: 'another-project') } it 'returns complete path to the issue' do expect(issue.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project#1' @@ -107,12 +107,12 @@ describe Issue do end it 'supports a cross-project reference' do - another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, name: 'another-project', namespace: project.namespace) expect(issue.to_reference(another_project)).to eq "sample-project#1" end context 'when same namespace / cross-project argument' do - let(:another_project) { create(:empty_project, namespace: namespace) } + let(:another_project) { create(:project, namespace: namespace) } it 'returns path to the issue with the project name' do expect(issue.to_reference(another_project)).to eq 'sample-project#1' @@ -121,7 +121,7 @@ describe Issue do context 'when different namespace / cross-project argument' do let(:another_namespace) { create(:namespace, path: 'another-namespace') } - let(:another_project) { create(:empty_project, path: 'another-project', namespace: another_namespace) } + let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) } it 'returns full path to the issue' do expect(issue.to_reference(another_project)).to eq 'sample-namespace/sample-project#1' @@ -191,14 +191,10 @@ describe Issue do end it 'returns the merge request to close this issue' do - mr - expect(issue.closed_by_merge_requests(mr.author)).to eq([mr]) end it "returns an empty array when the merge request is closed already" do - closed_mr - expect(issue.closed_by_merge_requests(closed_mr.author)).to eq([]) end @@ -209,7 +205,7 @@ describe Issue do describe '#referenced_merge_requests' do it 'returns the referenced merge requests' do - project = create(:empty_project, :public) + project = create(:project, :public) mr1 = create(:merge_request, source_project: project, @@ -242,7 +238,7 @@ describe Issue do end context 'user is reporter in project issue belongs to' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } before do @@ -258,7 +254,7 @@ describe Issue do context 'checking destination project also' do subject { issue.can_move?(user, to_project) } - let(:to_project) { create(:empty_project) } + let(:to_project) { create(:project) } context 'destination project allowed' do before do @@ -354,7 +350,7 @@ describe Issue do subject { create(:issue, project: create(:project, :repository)) } let(:backref_text) { "issue #{subject.to_reference}" } - let(:set_mentionable_text) { ->(txt){ subject.description = txt } } + let(:set_mentionable_text) { ->(txt) { subject.description = txt } } end it_behaves_like 'a Taskable' do @@ -380,7 +376,7 @@ describe Issue do describe '#participants' do context 'using a public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let!(:note1) do @@ -402,7 +398,7 @@ describe Issue do context 'using a private project' do it 'does not include mentioned users that do not have access to the project' do - project = create(:empty_project) + project = create(:project) user = create(:user) issue = create(:issue, project: project) @@ -420,7 +416,7 @@ describe Issue do it 'updates when assignees change' do user1 = create(:user) user2 = create(:user) - project = create(:empty_project) + project = create(:project) issue = create(:issue, assignees: [user1], project: project) project.add_developer(user1) project.add_developer(user2) @@ -490,7 +486,7 @@ describe Issue do let(:user) { create(:user) } context 'using a public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } it 'returns true for a regular issue' do issue = build(:issue, project: project) @@ -506,7 +502,7 @@ describe Issue do end context 'using an internal project' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } context 'using an internal user' do it 'returns true for a regular issue' do @@ -542,7 +538,7 @@ describe Issue do end context 'using a private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it 'returns false for a regular issue' do issue = build(:issue, project: project) @@ -578,7 +574,7 @@ describe Issue do context 'with a regular user that is a team member' do let(:user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'using a public project' do before do @@ -599,7 +595,7 @@ describe Issue do end context 'using an internal project' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } before do project.team << [user, Gitlab::Access::DEVELOPER] @@ -619,7 +615,7 @@ describe Issue do end context 'using a private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } before do project.team << [user, Gitlab::Access::DEVELOPER] @@ -640,7 +636,7 @@ describe Issue do end context 'with an admin user' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:admin) } it 'returns true for a regular issue' do @@ -659,7 +655,7 @@ describe Issue do describe '#publicly_visible?' do context 'using a public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } it 'returns true for a regular issue' do issue = build(:issue, project: project) @@ -675,7 +671,7 @@ describe Issue do end context 'using an internal project' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } it 'returns false for a regular issue' do issue = build(:issue, project: project) @@ -691,7 +687,7 @@ describe Issue do end context 'using a private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it 'returns false for a regular issue' do issue = build(:issue, project: project) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index d41717d0223..3508391c721 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe Key do - include EmailHelpers - +describe Key, :mailer do describe "Associations" do it { is_expected.to belong_to(:user) } end @@ -85,24 +83,16 @@ describe Key do expect(build(:key)).to be_valid end - it 'rejects an unfingerprintable key that contains a space' do + it 'accepts a key with newline charecters after stripping them' do key = build(:key) - - # Not always the middle, but close enough - key.key = key.key[0..100] + ' ' + key.key[101..-1] - - expect(key).not_to be_valid + key.key = key.key.insert(100, "\n") + key.key = key.key.insert(40, "\r\n") + expect(key).to be_valid end it 'rejects the unfingerprintable key (not a key)' do expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid end - - it 'rejects the multiple line key' do - key = build(:key) - key.key.tr!(' ', "\n") - expect(key).not_to be_valid - end end context 'callbacks' do diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index 8c74f1f9e86..d24d4cf7695 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe LfsObjectsProject do subject { create(:lfs_objects_project, project: project) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe 'associations' do it { is_expected.to belong_to(:project) } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 8bfd70b8d46..87513e18b25 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -57,7 +57,7 @@ describe Member do describe 'Scopes & finders' do before do - project = create(:empty_project, :public, :access_requestable) + project = create(:project, :public, :access_requestable) group = create(:group) @owner_user = create(:user).tap { |u| group.add_owner(u) } @owner = group.members.find_by(user_id: @owner_user.id) @@ -516,7 +516,7 @@ describe Member do describe "destroying a record", truncate: true do it "refreshes user's authorized projects" do - project = create(:empty_project, :private) + project = create(:project, :private) user = create(:user) member = project.team << [user, :reporter] diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 025fb2bf441..f1d1f37c78a 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -24,7 +24,7 @@ describe ProjectMember do describe '.add_user' do it 'adds the user as a member' do user = create(:user) - project = create(:empty_project) + project = create(:project) expect(project.users).not_to include(user) @@ -82,8 +82,8 @@ describe ProjectMember do describe '.import_team' do before do - @project_1 = create(:empty_project) - @project_2 = create(:empty_project) + @project_1 = create(:project) + @project_2 = create(:project) @user_1 = create :user @user_2 = create :user @@ -112,7 +112,7 @@ describe ProjectMember do describe '.add_users_to_projects' do it 'adds the given users to the given projects' do - projects = create_list(:empty_project, 2) + projects = create_list(:project, 2) users = create_list(:user, 2) described_class.add_users_to_projects( @@ -130,8 +130,8 @@ describe ProjectMember do describe '.truncate_teams' do before do - @project_1 = create(:empty_project) - @project_2 = create(:empty_project) + @project_1 = create(:project) + @project_2 = create(:project) @user_1 = create :user @user_2 = create :user diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b2dd02553c1..4aada17c8c0 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -237,7 +237,7 @@ describe MergeRequest do end describe '#to_reference' do - let(:project) { build(:empty_project, name: 'sample-project') } + let(:project) { build(:project, name: 'sample-project') } let(:merge_request) { build(:merge_request, target_project: project, iid: 1) } it 'returns a String reference to the object' do @@ -245,12 +245,12 @@ describe MergeRequest do end it 'supports a cross-project reference' do - another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, name: 'another-project', namespace: project.namespace) expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end it 'returns a String reference with the full path' do - expect(merge_request.to_reference(full: true)).to eq(project.path_with_namespace + '!1') + expect(merge_request.to_reference(full: true)).to eq(project.full_path + '!1') end end @@ -392,8 +392,8 @@ describe MergeRequest do describe '#for_fork?' do it 'returns true if the merge request is for a fork' do - subject.source_project = build_stubbed(:empty_project, namespace: create(:group)) - subject.target_project = build_stubbed(:empty_project, namespace: create(:group)) + subject.source_project = build_stubbed(:project, namespace: create(:group)) + subject.target_project = build_stubbed(:project, namespace: create(:group)) expect(subject.for_fork?).to be_truthy end @@ -681,7 +681,7 @@ describe MergeRequest do end it 'does not crash' do - expect{ subject.diverged_commits_count }.not_to raise_error + expect { subject.diverged_commits_count }.not_to raise_error end it 'returns 0' do @@ -753,7 +753,7 @@ describe MergeRequest do subject { create(:merge_request, :simple) } let(:backref_text) { "merge request #{subject.to_reference}" } - let(:set_mentionable_text) { ->(txt){ subject.description = txt } } + let(:set_mentionable_text) { ->(txt) { subject.description = txt } } end it_behaves_like 'a Taskable' do @@ -889,7 +889,7 @@ describe MergeRequest do end describe '#participants' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:mr) do create(:merge_request, source_project: project, target_project: project) @@ -932,7 +932,7 @@ describe MergeRequest do end describe '#check_if_can_be_merged' do - let(:project) { create(:empty_project, only_allow_merge_if_pipeline_succeeds: true) } + let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } subject { create(:merge_request, source_project: project, merge_status: :unchecked) } @@ -970,7 +970,7 @@ describe MergeRequest do end describe '#mergeable?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { create(:merge_request, source_project: project) } @@ -1055,7 +1055,7 @@ describe MergeRequest do end describe '#mergeable_ci_state?' do - let(:project) { create(:empty_project, only_allow_merge_if_pipeline_succeeds: true) } + let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } let(:pipeline) { create(:ci_empty_pipeline) } subject { build(:merge_request, target_project: project) } @@ -1098,7 +1098,7 @@ describe MergeRequest do end context 'when merges are not restricted to green builds' do - subject { build(:merge_request, target_project: build(:empty_project, only_allow_merge_if_pipeline_succeeds: false)) } + subject { build(:merge_request, target_project: build(:project, only_allow_merge_if_pipeline_succeeds: false)) } context 'and a failed pipeline is associated' do before do @@ -1332,8 +1332,8 @@ describe MergeRequest do end describe "#source_project_missing?" do - let(:project) { create(:empty_project) } - let(:fork_project) { create(:empty_project, forked_from_project: project) } + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1369,9 +1369,35 @@ describe MergeRequest do end end + describe '#merge_ongoing?' do + it 'returns true when merge process is ongoing for merge_jid' do + merge_request = create(:merge_request, merge_jid: 'foo') + + allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(1) + + expect(merge_request.merge_ongoing?).to be(true) + end + + it 'returns false when no merge process running for merge_jid' do + merge_request = build(:merge_request, merge_jid: 'foo') + + allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(0) + + expect(merge_request.merge_ongoing?).to be(false) + end + + it 'returns false when merge_jid is nil' do + merge_request = build(:merge_request, merge_jid: nil) + + expect(Gitlab::SidekiqStatus).not_to receive(:num_running) + + expect(merge_request.merge_ongoing?).to be(false) + end + end + describe "#closed_without_fork?" do - let(:project) { create(:empty_project) } - let(:fork_project) { create(:empty_project, forked_from_project: project) } + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1416,9 +1442,9 @@ describe MergeRequest do end context 'forked project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } - let(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user.namespace) } + let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } let!(:merge_request) do create(:closed_merge_request, diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index aa376e242e8..d3da0107d5c 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -21,7 +21,7 @@ describe Milestone do it { is_expected.to have_many(:issues) } end - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -42,7 +42,7 @@ describe Milestone do end it "accepts the same title in another project" do - project = create(:empty_project) + project = create(:project) new_milestone = described_class.new(project: project, title: milestone.title) expect(new_milestone).to be_valid @@ -197,9 +197,9 @@ describe Milestone do end describe '.upcoming_ids_by_projects' do - let(:project_1) { create(:empty_project) } - let(:project_2) { create(:empty_project) } - let(:project_3) { create(:empty_project) } + let(:project_1) { create(:project) } + let(:project_2) { create(:project) } + let(:project_3) { create(:project) } let(:projects) { [project_1, project_2, project_3] } let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now - 1.day) } @@ -230,21 +230,45 @@ describe Milestone do end describe '#to_reference' do - let(:project) { build(:empty_project, name: 'sample-project') } - let(:milestone) { build(:milestone, iid: 1, project: project) } + let(:group) { build_stubbed(:group) } + let(:project) { build_stubbed(:project, name: 'sample-project') } + let(:another_project) { build_stubbed(:project, name: 'another-project', namespace: project.namespace) } + + context 'for a project milestone' do + let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') } + + it 'returns a String reference to the object' do + expect(milestone.to_reference).to eq '%1' + end - it 'returns a String reference to the object' do - expect(milestone.to_reference).to eq "%1" + it 'returns a reference by name when the format is set to :name' do + expect(milestone.to_reference(format: :name)).to eq '%"milestone"' + end + + it 'supports a cross-project reference' do + expect(milestone.to_reference(another_project)).to eq 'sample-project%1' + end end - it 'supports a cross-project reference' do - another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) - expect(milestone.to_reference(another_project)).to eq "sample-project%1" + context 'for a group milestone' do + let(:milestone) { build_stubbed(:milestone, iid: 1, group: group, name: 'milestone') } + + it 'returns nil with the default format' do + expect(milestone.to_reference).to be_nil + end + + it 'returns a reference by name when the format is set to :name' do + expect(milestone.to_reference(format: :name)).to eq '%"milestone"' + end + + it 'does not supports cross-project references' do + expect(milestone.to_reference(another_project, format: :name)).to eq '%"milestone"' + end end end describe '#participants' do - let(:project) { build(:empty_project, name: 'sample-project') } + let(:project) { build(:project, name: 'sample-project') } let(:milestone) { build(:milestone, iid: 1, project: project) } it 'returns participants without duplicates' do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index f12fe226e6b..1a00c50690c 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -111,7 +111,7 @@ describe Namespace do let(:namespace) { create :namespace } let(:project1) do - create(:empty_project, + create(:project, namespace: namespace, statistics: build(:project_statistics, storage_size: 606, @@ -121,7 +121,7 @@ describe Namespace do end let(:project2) do - create(:empty_project, + create(:project, namespace: namespace, statistics: build(:project_statistics, storage_size: 60, @@ -177,7 +177,7 @@ describe Namespace do stub_container_registry_config(enabled: true) stub_container_registry_tags(repository: :any, tags: ['tag']) - create(:empty_project, namespace: @namespace, container_repositories: [container_repository]) + create(:project, namespace: @namespace, container_repositories: [container_repository]) allow(@namespace).to receive(:path_was).and_return(@namespace.path) allow(@namespace).to receive(:path).and_return('new_path') diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index cbe6d42ef53..b214074fdce 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -46,7 +46,7 @@ describe Note do context 'when noteable and note project differ' do subject do build(:note, noteable: build_stubbed(:issue), - project: build_stubbed(:empty_project)) + project: build_stubbed(:project)) end it { is_expected.to be_invalid } @@ -97,8 +97,8 @@ describe Note do describe 'authorization' do before do - @p1 = create(:empty_project) - @p2 = create(:empty_project) + @p1 = create(:project) + @p2 = create(:project) @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) @@ -195,10 +195,10 @@ describe Note do describe "cross_reference_not_visible_for?" do let(:private_user) { create(:user) } - let(:private_project) { create(:empty_project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } + let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:empty_project, :public) } + let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:note) do @@ -241,7 +241,7 @@ describe Note do describe '#participants' do it 'includes the note author' do - project = create(:empty_project, :public) + project = create(:project, :public) issue = create(:issue, project: project) note = create(:note_on_issue, noteable: issue, project: project) diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 07e296424ca..2a0d102d3fe 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -63,24 +63,20 @@ RSpec.describe NotificationSetting do end end - describe 'event_enabled?' do + describe '#event_enabled?' do before do subject.update!(user: create(:user)) end context 'for an event with a matching column name' do - before do - subject.update!(events: { new_note: true }.to_json) - end - it 'returns the value of the column' do - subject.update!(new_note: false) + subject.update!(new_note: true) - expect(subject.event_enabled?(:new_note)).to be(false) + expect(subject.event_enabled?(:new_note)).to be(true) end context 'when the column has a nil value' do - it 'returns the value from the events hash' do + it 'returns false' do expect(subject.event_enabled?(:new_note)).to be(false) end end diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb index ee6bdc39c8c..9e7e525b2c0 100644 --- a/spec/models/project_authorization_spec.rb +++ b/spec/models/project_authorization_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe ProjectAuthorization do let(:user) { create(:user) } - let(:project1) { create(:empty_project) } - let(:project2) { create(:empty_project) } + let(:project1) { create(:project) } + let(:project2) { create(:project) } describe '.insert_authorizations' do it 'inserts the authorizations' do diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 580c83c12c0..de3e86b627f 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeature do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '.quoted_access_level_column' do @@ -47,7 +47,7 @@ describe ProjectFeature do it "returns true when user is a member of project group" do group = create(:group) - project = create(:empty_project, namespace: group) + project = create(:project, namespace: group) group.add_developer(user) features.each do |feature| diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index d68d8b719cd..b3513c80150 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -32,7 +32,7 @@ describe ProjectGroupLink do describe "destroying a record", truncate: true do it "refreshes group users' authorized projects" do - project = create(:empty_project, :private) + project = create(:project, :private) group = create(:group) reporter = create(:user) group_users = group.users diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index add7e85f388..689d4e505e5 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -10,7 +10,7 @@ describe ProjectLabel do context 'validates if title must not exist at group level' do let(:group) { create(:group, name: 'gitlab-org') } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } before do create(:group_label, group: group, title: 'Bug') @@ -33,7 +33,7 @@ describe ProjectLabel do end it 'does not returns error if project does not belong to group' do - another_project = create(:empty_project) + another_project = create(:project) label = described_class.new(project: another_project, title: 'Bug') label.valid? @@ -66,7 +66,7 @@ describe ProjectLabel do describe '#subject' do it 'aliases project to subject' do - subject = described_class.new(project: build(:empty_project)) + subject = described_class.new(project: build(:project)) expect(subject.subject).to be(subject.project) end @@ -100,19 +100,19 @@ describe ProjectLabel do end context 'cross project reference' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'using name' do it 'returns cross reference with label name' do expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.path_with_namespace}~"#{label.name}") + .to eq %Q(#{label.project.full_path}~"#{label.name}") end end context 'using id' do it 'returns cross reference with label id' do expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.path_with_namespace}~#{label.id}) + .to eq %Q(#{label.project.full_path}~#{label.id}) end end end diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 4684c970885..04440d890aa 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -18,7 +18,7 @@ describe AsanaService do describe 'Execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } def create_data_for_commits(*messages) { diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 82f02126de1..85baaccf035 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -7,7 +7,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do subject(:service) do described_class.create( - project: create(:empty_project), + project: create(:project), properties: { bamboo_url: bamboo_url, username: 'mic', diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 3f7eb33e08a..1615a93a4ca 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BuildkiteService, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject(:service) do described_class.create( diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb index 413ceed73bf..3aa1039d8bf 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/project_services/chat_notification_service_spec.rb @@ -12,7 +12,7 @@ describe ChatNotificationService do describe '#can_test?' do context 'with empty repository' do it 'returns true' do - subject.project = create(:empty_project, :empty_repo) + subject.project = create(:project, :empty_repo) expect(subject.can_test?).to be true end @@ -20,7 +20,7 @@ describe ChatNotificationService do context 'with repository' do it 'returns true' do - subject.project = create(:project) + subject.project = create(:project, :repository) expect(subject.can_test?).to be true end diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 22cd9d5e31e..25e6ce7e804 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -27,7 +27,7 @@ describe ExternalWikiService do end describe 'External wiki' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when it is active' do before do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index d19dab8fd39..3237b660a16 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -8,21 +8,21 @@ describe GitlabIssueTrackerService do describe 'Validations' do context 'when service is active' do - subject { described_class.new(project: create(:empty_project), active: true) } + subject { described_class.new(project: create(:project), active: true) } it { is_expected.to validate_presence_of(:issues_url) } it_behaves_like 'issue tracker service URL attribute', :issues_url end context 'when service is inactive' do - subject { described_class.new(project: create(:empty_project), active: false) } + subject { described_class.new(project: create(:project), active: false) } it { is_expected.not_to validate_presence_of(:issues_url) } end end describe 'project and issue urls' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:service) { project.create_gitlab_issue_tracker_service(active: true) } context 'with absolute urls' do @@ -31,9 +31,9 @@ describe GitlabIssueTrackerService do end it 'gives the correct path' do - expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues") - expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/new") - expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/432") + expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/issues") + expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/issues/new") + expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/issues/432") end end @@ -43,9 +43,9 @@ describe GitlabIssueTrackerService do end it 'gives the correct path' do - expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") - expect(service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") - expect(service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") + expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.full_path}/issues") + expect(service.new_issue_path).to eq("/gitlab/root/#{project.full_path}/issues/new") + expect(service.issue_path(432)).to eq("/gitlab/root/#{project.full_path}/issues/432") end end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 8f34b44930e..63bf131cfc5 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -29,7 +29,7 @@ describe JiraService do context 'validating urls' do let(:service) do described_class.new( - project: create(:empty_project), + project: create(:project), active: true, username: 'username', password: 'test', @@ -74,7 +74,7 @@ describe JiraService do describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:merge_request) { create(:merge_request) } before do @@ -135,7 +135,7 @@ describe JiraService do body: hash_including( GlobalID: "GitLab", object: { - url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{merge_request.diff_head_sha}", + url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}", title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.", icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, status: { resolved: true } @@ -153,13 +153,22 @@ describe JiraService do expect(WebMock).not_to have_requested(:post, @remote_link_url) end + it "does not send comment or remote links to issues with unknown resolution" do + allow_any_instance_of(JIRA::Resource::Issue).to receive(:respond_to?).with(:resolution).and_return(false) + + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) + + expect(WebMock).not_to have_requested(:post, @comment_url) + expect(WebMock).not_to have_requested(:post, @remote_link_url) + end + it "references the GitLab commit/merge request" do stub_config_setting(base_url: custom_base_url) @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( - body: /#{custom_base_url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ + body: /#{custom_base_url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/ ).once end @@ -174,7 +183,7 @@ describe JiraService do @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( - body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ + body: /#{Gitlab.config.gitlab.url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/ ).once end @@ -233,7 +242,7 @@ describe JiraService do end describe "Stored password invalidation" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context "when a password was previously set" do before do @@ -338,7 +347,7 @@ describe JiraService do end describe 'description and title' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when it is not set' do before do @@ -373,7 +382,7 @@ describe JiraService do end describe 'project and issue urls' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when gitlab.yml was initialized' do before do diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 4c21c8b88bd..522cf15f3ba 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -4,7 +4,7 @@ describe MattermostSlashCommandsService do it_behaves_like "chat slash commands service" context 'Mattermost API' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:service) { project.build_mattermost_slash_commands_service } let(:user) { create(:user) } diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index 03932895b0e..5faab9ba38b 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe PipelinesEmailService do - include EmailHelpers - +describe PipelinesEmailService, :mailer do let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end @@ -14,10 +12,6 @@ describe PipelinesEmailService do Gitlab::DataBuilder::Pipeline.build(pipeline) end - before do - reset_delivered_emails! - end - describe 'Validations' do context 'when service is active' do before do diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb index aea674c4f6b..0d95f454819 100644 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ b/spec/models/project_services/slack_slash_commands_service_spec.rb @@ -5,7 +5,7 @@ describe SlackSlashCommandsService do describe '#trigger' do context 'when an auth url is generated' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:params) do { team_domain: 'http://domain.tld', diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 47fd0c79e0b..43a0ed99296 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -7,7 +7,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do subject(:service) do described_class.create( - project: create(:empty_project), + project: create(:project), properties: { teamcity_url: teamcity_url, username: 'mic', diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 473b7a88d61..4a80f41cdc9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -82,7 +82,7 @@ describe Project do end describe '#members & #requesters' do - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:requester) { create(:user) } let(:developer) { create(:user) } before do @@ -131,7 +131,7 @@ describe Project do end describe 'validation' do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } @@ -154,7 +154,7 @@ describe Project do it { is_expected.to validate_presence_of(:repository_storage) } it 'does not allow new projects beyond user limits' do - project2 = build(:empty_project) + project2 = build(:project) allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) expect(project2).not_to be_valid expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) @@ -163,7 +163,7 @@ describe Project do describe 'wiki path conflict' do context "when the new path has been used by the wiki of other Project" do it 'has an error on the name attribute' do - new_project = build_stubbed(:empty_project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") + new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -172,8 +172,8 @@ describe Project do context "when the new wiki path has been used by the path of other Project" do it 'has an error on the name attribute' do - project_with_wiki_suffix = create(:empty_project, path: 'foo.wiki') - new_project = build_stubbed(:empty_project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') + project_with_wiki_suffix = create(:project, path: 'foo.wiki') + new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -182,7 +182,7 @@ describe Project do end context 'repository storages inclussion' do - let(:project2) { build(:empty_project, repository_storage: 'missing') } + let(:project2) { build(:project, repository_storage: 'missing') } before do storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } } @@ -196,44 +196,44 @@ describe Project do end it 'does not allow an invalid URI as import_url' do - project2 = build(:empty_project, import_url: 'invalid://') + project2 = build(:project, import_url: 'invalid://') expect(project2).not_to be_valid end it 'does allow a valid URI as import_url' do - project2 = build(:empty_project, import_url: 'ssh://test@gitlab.com/project.git') + project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git') expect(project2).to be_valid end it 'allows an empty URI' do - project2 = build(:empty_project, import_url: '') + project2 = build(:project, import_url: '') expect(project2).to be_valid end it 'does not produce import data on an empty URI' do - project2 = build(:empty_project, import_url: '') + project2 = build(:project, import_url: '') expect(project2.import_data).to be_nil end it 'does not produce import data on an invalid URI' do - project2 = build(:empty_project, import_url: 'test://') + project2 = build(:project, import_url: 'test://') expect(project2.import_data).to be_nil end it "does not allow blocked import_url localhost" do - project2 = build(:empty_project, import_url: 'http://localhost:9000/t.git') + project2 = build(:project, import_url: 'http://localhost:9000/t.git') expect(project2).to be_invalid expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') end it "does not allow blocked import_url port" do - project2 = build(:empty_project, import_url: 'http://github.com:25/t.git') + project2 = build(:project, import_url: 'http://github.com:25/t.git') expect(project2).to be_invalid expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') @@ -241,11 +241,11 @@ describe Project do describe 'project pending deletion' do let!(:project_pending_deletion) do - create(:empty_project, + create(:project, pending_delete: true) end let(:new_project) do - build(:empty_project, + build(:project, name: project_pending_deletion.name, namespace: project_pending_deletion.namespace) end @@ -290,12 +290,12 @@ describe Project do describe 'project token' do it 'sets an random token if none provided' do - project = FactoryGirl.create :empty_project, runners_token: '' + project = FactoryGirl.create :project, runners_token: '' expect(project.runners_token).not_to eq('') end it 'does not set an random token if one provided' do - project = FactoryGirl.create :empty_project, runners_token: 'my-token' + project = FactoryGirl.create :project, runners_token: 'my-token' expect(project.runners_token).to eq('my-token') end end @@ -306,6 +306,7 @@ describe Project do it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } + it { is_expected.to respond_to(:full_path) } end describe 'delegation' do @@ -322,7 +323,7 @@ describe Project do describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } - let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) } + let(:project) { create(:project, path: 'sample-project', namespace: namespace) } let(:group) { create(:group, name: 'Group', path: 'sample-group', owner: owner) } context 'when nil argument' do @@ -346,7 +347,7 @@ describe Project do end context 'when cross namespace project argument' do - let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + let(:another_namespace_project) { create(:project, name: 'another-project') } it 'returns complete path to the project' do expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' @@ -354,7 +355,7 @@ describe Project do end context 'when same namespace / cross-project argument' do - let(:another_project) { create(:empty_project, namespace: namespace) } + let(:another_project) { create(:project, namespace: namespace) } it 'returns path to the project' do expect(project.to_reference(another_project)).to eq 'sample-project' @@ -363,7 +364,7 @@ describe Project do context 'when different namespace / cross-project argument' do let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) } - let(:another_project) { create(:empty_project, path: 'another-project', namespace: another_namespace) } + let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) } it 'returns full path to the project' do expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project' @@ -388,7 +389,7 @@ describe Project do describe '#to_human_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } - let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) } + let(:project) { create(:project, name: 'Sample project', namespace: namespace) } context 'when nil argument' do it 'returns nil' do @@ -403,7 +404,7 @@ describe Project do end context 'when cross namespace project argument' do - let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + let(:another_namespace_project) { create(:project, name: 'another-project') } it 'returns complete name with namespace of the project' do expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' @@ -411,7 +412,7 @@ describe Project do end context 'when same namespace / cross-project argument' do - let(:another_project) { create(:empty_project, namespace: namespace) } + let(:another_project) { create(:project, namespace: namespace) } it 'returns name of the project' do expect(project.to_human_reference(another_project)).to eq 'Sample project' @@ -420,7 +421,7 @@ describe Project do end describe '#repository_storage_path' do - let(:project) { create(:empty_project, repository_storage: 'custom') } + let(:project) { create(:project, repository_storage: 'custom') } before do FileUtils.mkdir('tmp/tests/custom_repositories') @@ -443,7 +444,7 @@ describe Project do end describe "#web_url" do - let(:project) { create(:empty_project, path: "somewhere") } + let(:project) { create(:project, path: "somewhere") } it 'returns the full web URL for this repo' do expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere") @@ -451,7 +452,7 @@ describe Project do end describe "#new_issue_address" do - let(:project) { create(:empty_project, path: "somewhere") } + let(:project) { create(:project, path: "somewhere") } let(:user) { create(:user) } context 'incoming email enabled' do @@ -460,7 +461,7 @@ describe Project do end it 'returns the address to create a new issue' do - address = "p+#{project.path_with_namespace}+#{user.incoming_email_token}@gl.ab" + address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab" expect(project.new_issue_address(user)).to eq(address) end @@ -480,7 +481,7 @@ describe Project do describe 'last_activity methods' do let(:timestamp) { 2.hours.ago } # last_activity_at gets set to created_at upon creation - let(:project) { create(:empty_project, created_at: timestamp, updated_at: timestamp) } + let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -505,7 +506,7 @@ describe Project do end describe '#get_issue' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -580,7 +581,7 @@ describe Project do end describe '#issue_exists?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'is truthy when issue exists' do expect(project).to receive(:get_issue).and_return(double) @@ -597,7 +598,7 @@ describe Project do context 'with namespace' do before do @group = create :group, name: 'gitlab' - @project = create(:empty_project, name: 'gitlabhq', namespace: @group) + @project = create(:project, name: 'gitlabhq', namespace: @group) end it { expect(@project.to_param).to eq('gitlabhq') } @@ -605,7 +606,7 @@ describe Project do context 'with invalid path' do it 'returns previous path to keep project suitable for use in URLs when persisted' do - project = create(:empty_project, path: 'gitlab') + project = create(:project, path: 'gitlab') project.path = 'foo&bar' expect(project).not_to be_valid @@ -613,7 +614,7 @@ describe Project do end it 'returns current path when new record' do - project = build(:empty_project, path: 'gitlab') + project = build(:project, path: 'gitlab') project.path = 'foo&bar' expect(project).not_to be_valid @@ -632,7 +633,7 @@ describe Project do describe '#default_issues_tracker?' do it "is true if used internal tracker" do - project = build(:empty_project) + project = build(:project) expect(project.default_issues_tracker?).to be_truthy end @@ -646,7 +647,7 @@ describe Project do end describe '#external_issue_tracker' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } context 'on existing projects with no value for has_external_issue_tracker' do @@ -681,7 +682,7 @@ describe Project do end describe '#cache_has_external_issue_tracker' do - let(:project) { create(:empty_project, has_external_issue_tracker: nil) } + let(:project) { create(:project, has_external_issue_tracker: nil) } it 'stores true if there is any external_issue_tracker' do services = double(:service, external_issue_trackers: [RedmineService.new]) @@ -703,9 +704,9 @@ describe Project do end describe '#has_wiki?' do - let(:no_wiki_project) { create(:empty_project, :wiki_disabled, has_external_wiki: false) } - let(:wiki_enabled_project) { create(:empty_project) } - let(:external_wiki_project) { create(:empty_project, has_external_wiki: true) } + let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) } + let(:wiki_enabled_project) { create(:project) } + let(:external_wiki_project) { create(:project, has_external_wiki: true) } it 'returns true if project is wiki enabled or has external wiki' do expect(wiki_enabled_project).to have_wiki @@ -715,7 +716,7 @@ describe Project do end describe '#external_wiki' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'with an active external wiki' do before do @@ -769,7 +770,7 @@ describe Project do it 'counts stars from multiple users' do user1 = create :user user2 = create :user - project = create(:empty_project, :public) + project = create(:project, :public) expect(project.star_count).to eq(0) @@ -791,8 +792,8 @@ describe Project do it 'counts stars on the right project' do user = create :user - project1 = create(:empty_project, :public) - project2 = create(:empty_project, :public) + project1 = create(:project, :public) + project2 = create(:project, :public) expect(project1.star_count).to eq(0) expect(project2.star_count).to eq(0) @@ -824,7 +825,7 @@ describe Project do end describe '#avatar_type' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'is true if avatar is image' do project.update_attribute(:avatar, 'uploads/avatar.png') @@ -840,10 +841,10 @@ describe Project do describe '#avatar_url' do subject { project.avatar_url } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when avatar file is uploaded' do - let(:project) { create(:empty_project, :with_avatar) } + let(:project) { create(:project, :with_avatar) } let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } @@ -868,7 +869,7 @@ describe Project do end context 'when git repo is empty' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it { is_expected.to eq nil } end @@ -909,7 +910,7 @@ describe Project do end describe '#builds_enabled' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { project.builds_enabled } @@ -920,7 +921,7 @@ describe Project do subject { described_class.with_shared_runners } context 'when shared runners are enabled for project' do - let!(:project) { create(:empty_project, shared_runners_enabled: true) } + let!(:project) { create(:project, shared_runners_enabled: true) } it "returns a project" do is_expected.to eq([project]) @@ -928,7 +929,7 @@ describe Project do end context 'when shared runners are disabled for project' do - let!(:project) { create(:empty_project, shared_runners_enabled: false) } + let!(:project) { create(:project, shared_runners_enabled: false) } it "returns an empty array" do is_expected.to be_empty @@ -938,8 +939,8 @@ describe Project do describe '.cached_count', :use_clean_rails_memory_store_caching do let(:group) { create(:group, :public) } - let!(:project1) { create(:empty_project, :public, group: group) } - let!(:project2) { create(:empty_project, :public, group: group) } + let!(:project1) { create(:project, :public, group: group) } + let!(:project2) { create(:project, :public, group: group) } it 'returns total project count' do expect(described_class).to receive(:count).once.and_call_original @@ -952,8 +953,8 @@ describe Project do describe '.trending' do let(:group) { create(:group, :public) } - let(:project1) { create(:empty_project, :public, group: group) } - let(:project2) { create(:empty_project, :public, group: group) } + let(:project1) { create(:project, :public, group: group) } + let(:project2) { create(:project, :public, group: group) } before do 2.times do @@ -984,9 +985,9 @@ describe Project do it 'returns only projects starred by the given user' do user1 = create(:user) user2 = create(:user) - project1 = create(:empty_project) - project2 = create(:empty_project) - create(:empty_project) + project1 = create(:project) + project2 = create(:project) + create(:project) user1.toggle_star(project1) user2.toggle_star(project2) @@ -995,7 +996,7 @@ describe Project do end describe '.visible_to_user' do - let!(:project) { create(:empty_project, :private) } + let!(:project) { create(:project, :private) } let!(:user) { create(:user) } subject { described_class.visible_to_user(user) } @@ -1014,7 +1015,7 @@ describe Project do end context 'repository storage by default' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do storages = { @@ -1032,7 +1033,7 @@ describe Project do end context 'shared runners by default' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { project.shared_runners_enabled } @@ -1054,7 +1055,7 @@ describe Project do end describe '#any_runners' do - let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) } + let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) } let(:specific_runner) { create(:ci_runner) } let(:shared_runner) { create(:ci_runner, :shared) } @@ -1102,7 +1103,7 @@ describe Project do subject { project.shared_runners } context 'when shared runners are enabled for project' do - let!(:project) { create(:empty_project, shared_runners_enabled: true) } + let!(:project) { create(:project, shared_runners_enabled: true) } it "returns a list of shared runners" do is_expected.to eq([runner]) @@ -1110,7 +1111,7 @@ describe Project do end context 'when shared runners are disabled for project' do - let!(:project) { create(:empty_project, shared_runners_enabled: false) } + let!(:project) { create(:project, shared_runners_enabled: false) } it "returns a empty list" do is_expected.to be_empty @@ -1119,7 +1120,7 @@ describe Project do end describe '#visibility_level_allowed?' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } context 'when checking on non-forked project' do it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } @@ -1128,8 +1129,8 @@ describe Project do end context 'when checking on forked project' do - let(:project) { create(:empty_project, :internal) } - let(:forked_project) { create(:empty_project, forked_from_project: project) } + let(:project) { create(:project, :internal) } + let(:forked_project) { create(:project, forked_from_project: project) } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy } @@ -1138,7 +1139,7 @@ describe Project do end describe '#pages_deployed?' do - let(:project) { create :empty_project } + let(:project) { create :project } subject { project.pages_deployed? } @@ -1155,8 +1156,35 @@ describe Project do end end + describe '#pages_url' do + let(:group) { create :group, name: group_name } + let(:project) { create :project, namespace: group, name: project_name } + let(:domain) { 'Example.com' } + + subject { project.pages_url } + + before do + allow(Settings.pages).to receive(:host).and_return(domain) + allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com') + end + + context 'group page' do + let(:group_name) { 'Group' } + let(:project_name) { 'group.example.com' } + + it { is_expected.to eq("http://group.example.com") } + end + + context 'project page' do + let(:group_name) { 'Group' } + let(:project_name) { 'Project' } + + it { is_expected.to eq("http://group.example.com/project") } + end + end + describe '.search' do - let(:project) { create(:empty_project, description: 'kitten mittens') } + let(:project) { create(:project, description: 'kitten mittens') } it 'returns projects with a matching name' do expect(described_class.search(project.name)).to eq([project]) @@ -1213,7 +1241,7 @@ describe Project do end describe 'with pending_delete project' do - let(:pending_delete_project) { create(:empty_project, pending_delete: true) } + let(:pending_delete_project) { create(:project, pending_delete: true) } it 'shows pending deletion project' do search_result = described_class.search(pending_delete_project.name) @@ -1273,7 +1301,7 @@ describe Project do subject { project.rename_repo } - it { expect{subject}.to raise_error(StandardError) } + it { expect {subject}.to raise_error(StandardError) } end end @@ -1299,7 +1327,7 @@ describe Project do end describe '.search_by_title' do - let(:project) { create(:empty_project, name: 'kittens') } + let(:project) { create(:project, name: 'kittens') } it 'returns projects with a matching name' do expect(described_class.search_by_title(project.name)).to eq([project]) @@ -1318,8 +1346,8 @@ describe Project do let(:private_group) { create(:group, visibility_level: 0) } let(:internal_group) { create(:group, visibility_level: 10) } - let(:private_project) { create :empty_project, :private, group: private_group } - let(:internal_project) { create :empty_project, :internal, group: internal_group } + let(:private_project) { create :project, :private, group: private_group } + let(:internal_project) { create :project, :internal, group: internal_group } context 'when group is private project can not be internal' do it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey } @@ -1341,7 +1369,7 @@ describe Project do context 'using a regular repository' do it 'creates the repository' do expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.path_with_namespace) + .with(project.repository_storage_path, project.disk_path) .and_return(true) expect(project.repository).to receive(:after_create) @@ -1351,7 +1379,7 @@ describe Project do it 'adds an error if the repository could not be created' do expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.path_with_namespace) + .with(project.repository_storage_path, project.disk_path) .and_return(false) expect(project.repository).not_to receive(:after_create) @@ -1384,7 +1412,7 @@ describe Project do .and_return(false) allow(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.path_with_namespace) + .with(project.repository_storage_path, project.disk_path) .and_return(true) expect(project).to receive(:create_repository).with(force: true) @@ -1408,7 +1436,7 @@ describe Project do .and_return(false) expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.path_with_namespace) + .with(project.repository_storage_path, project.disk_path) .and_return(true) project.ensure_repository @@ -1416,7 +1444,7 @@ describe Project do end describe '#user_can_push_to_empty_repo?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } it 'returns false when default_branch_protection is in full protection and user is developer' do @@ -1455,7 +1483,7 @@ describe Project do end describe '#container_registry_url' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { project.container_registry_url } @@ -1482,7 +1510,7 @@ describe Project do end describe '#has_container_registry_tags?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'when container registry is enabled' do before do @@ -1546,7 +1574,7 @@ describe Project do end describe '#ci_config_path=' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'sets nil' do project.update!(ci_config_path: nil) @@ -1568,11 +1596,11 @@ describe Project do end describe 'Project import job' do - let(:project) { create(:empty_project, import_url: generate(:url)) } + let(:project) { create(:project, import_url: generate(:url)) } before do allow_any_instance_of(Gitlab::Shell).to receive(:import_repository) - .with(project.repository_storage_path, project.path_with_namespace, project.import_url) + .with(project.repository_storage_path, project.disk_path, project.import_url) .and_return(true) expect_any_instance_of(Repository).to receive(:after_import) @@ -1605,7 +1633,7 @@ describe Project do end it 'does not perform housekeeping when project repository does not exist' do - project = create(:empty_project, :import_started, import_type: :github) + project = create(:project, :import_started, import_type: :github) project.import_finish @@ -1613,7 +1641,7 @@ describe Project do end it 'does not perform housekeeping when project does not have a valid import type' do - project = create(:empty_project, :import_started, import_type: nil) + project = create(:project, :import_started, import_type: nil) project.import_finish @@ -1710,7 +1738,7 @@ describe Project do it 'schedules a RepositoryForkWorker job' do expect(RepositoryForkWorker).to receive(:perform_async) .with(project.id, forked_from_project.repository_storage_path, - forked_from_project.path_with_namespace, project.namespace.full_path) + forked_from_project.disk_path, project.namespace.full_path) project.add_import_job end @@ -1718,7 +1746,7 @@ describe Project do context 'not forked' do it 'schedules a RepositoryImportWorker job' do - project = create(:empty_project, import_url: generate(:url)) + project = create(:project, import_url: generate(:url)) expect(RepositoryImportWorker).to receive(:perform_async).with(project.id) @@ -1728,19 +1756,19 @@ describe Project do end describe '#gitlab_project_import?' do - subject(:project) { build(:empty_project, import_type: 'gitlab_project') } + subject(:project) { build(:project, import_type: 'gitlab_project') } it { expect(project.gitlab_project_import?).to be true } end describe '#gitea_import?' do - subject(:project) { build(:empty_project, import_type: 'gitea') } + subject(:project) { build(:project, import_type: 'gitea') } it { expect(project.gitea_import?).to be true } end describe '#lfs_enabled?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } shared_examples 'project overrides group' do it 'returns true when enabled in project' do @@ -1804,6 +1832,11 @@ describe Project do describe '#change_head' do let(:project) { create(:project, :repository) } + it 'returns error if branch does not exist' do + expect(project.change_head('unexisted-branch')).to be false + expect(project.errors.size).to eq(1) + end + it 'calls the before_change_head and after_change_head methods' do expect(project.repository).to receive(:before_change_head) expect(project.repository).to receive(:after_change_head) @@ -1830,7 +1863,7 @@ describe Project do end describe '#pushes_since_gc' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } after do project.reset_pushes_since_gc @@ -1852,7 +1885,7 @@ describe Project do end describe '#increment_pushes_since_gc' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } after do project.reset_pushes_since_gc @@ -1866,7 +1899,7 @@ describe Project do end describe '#reset_pushes_since_gc' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } after do project.reset_pushes_since_gc @@ -1883,7 +1916,7 @@ describe Project do describe '#deployment_variables' do context 'when project has no deployment service' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'returns an empty array' do expect(project.deployment_variables).to eq [] @@ -1902,7 +1935,7 @@ describe Project do end describe '#secret_variables_for' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:secret_variable) do create(:ci_variable, value: 'secret', project: project) @@ -1949,7 +1982,7 @@ describe Project do end describe '#protected_for?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { project.protected_for?('ref') } @@ -1986,7 +2019,7 @@ describe Project do end describe '#update_project_statistics' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it "is called after creation" do expect(project.statistics).to be_a ProjectStatistics @@ -2006,9 +2039,9 @@ describe Project do end describe 'inside_path' do - let!(:project1) { create(:empty_project, namespace: create(:namespace, path: 'name_pace')) } - let!(:project2) { create(:empty_project) } - let!(:project3) { create(:empty_project, namespace: create(:namespace, path: 'namespace')) } + let!(:project1) { create(:project, namespace: create(:namespace, path: 'name_pace')) } + let!(:project2) { create(:project) } + let!(:project3) { create(:project, namespace: create(:namespace, path: 'namespace')) } let!(:path) { project1.namespace.full_path } it 'returns correct project' do @@ -2017,7 +2050,7 @@ describe Project do end describe '#route_map_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:route_map) do <<-MAP.strip_heredoc - source: /source/(.*)/ @@ -2054,7 +2087,7 @@ describe Project do end describe '#public_path_for_source_path' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:route_map) do Gitlab::RouteMap.new(<<-MAP.strip_heredoc) - source: /source/(.*)/ @@ -2093,13 +2126,13 @@ describe Project do end describe '#parent' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it { expect(project.parent).to eq(project.namespace) } end describe '#parent_changed?' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.namespace_id = 7 @@ -2125,7 +2158,7 @@ describe Project do end context 'top-level group' do - let(:project) { create :empty_project, namespace: group, name: project_name } + let(:project) { create :project, namespace: group, name: project_name } context 'group page' do let(:project_name) { 'group.example.com' } @@ -2141,7 +2174,7 @@ describe Project do end context 'nested group' do - let(:project) { create :empty_project, namespace: nested_group, name: project_name } + let(:project) { create :project, namespace: nested_group, name: project_name } let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" } context 'group page' do @@ -2159,7 +2192,7 @@ describe Project do end describe '#http_url_to_repo' do - let(:project) { create :empty_project } + let(:project) { create :project } it 'returns the url to the repo without a username' do expect(project.http_url_to_repo).to eq("#{project.web_url}.git") @@ -2168,7 +2201,7 @@ describe Project do end describe '#pipeline_status' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'builds a pipeline status' do expect(project.pipeline_status).to be_a(Gitlab::Cache::Ci::ProjectPipelineStatus) end @@ -2199,7 +2232,7 @@ describe Project do describe '#last_repository_updated_at' do it 'sets to created_at upon creation' do - project = create(:empty_project, created_at: 2.hours.ago) + project = create(:project, created_at: 2.hours.ago) expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i) end @@ -2209,10 +2242,10 @@ describe Project do let!(:user) { create(:user) } let!(:private_project) do - create(:empty_project, :private, creator: user, namespace: user.namespace) + create(:project, :private, creator: user, namespace: user.namespace) end - let!(:public_project) { create(:empty_project, :public) } + let!(:public_project) { create(:project, :public) } context 'with a user' do let(:projects) do @@ -2238,19 +2271,43 @@ describe Project do end describe '#remove_private_deploy_keys' do - it 'removes the private deploy keys of a project' do - project = create(:empty_project) + let!(:project) { create(:project) } + + context 'for a private deploy key' do + let!(:key) { create(:deploy_key, public: false) } + let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) } + + context 'when the key is not linked to another project' do + it 'removes the key' do + project.remove_private_deploy_keys - private_key = create(:deploy_key, public: false) - public_key = create(:deploy_key, public: true) + expect(project.deploy_keys).not_to include(key) + end + end - create(:deploy_keys_project, deploy_key: private_key, project: project) - create(:deploy_keys_project, deploy_key: public_key, project: project) + context 'when the key is linked to another project' do + before do + another_project = create(:project) + create(:deploy_keys_project, deploy_key: key, project: another_project) + end - project.remove_private_deploy_keys + it 'does not remove the key' do + project.remove_private_deploy_keys - expect(project.deploy_keys.where(public: false).any?).to eq(false) - expect(project.deploy_keys.where(public: true).any?).to eq(true) + expect(project.deploy_keys).to include(key) + end + end + end + + context 'for a public deploy key' do + let!(:key) { create(:deploy_key, public: true) } + let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) } + + it 'does not remove the key' do + project.remove_private_deploy_keys + + expect(project.deploy_keys).to include(key) + end end end end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index be1b37730b1..59e20e84c2f 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe ProjectStatistics do - let(:project) { create :empty_project } + let(:project) { create :project } let(:statistics) { project.statistics } describe 'constants' do diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 68228a038a8..314824b32da 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -7,7 +7,7 @@ describe ProjectTeam do let(:nonmember) { create(:user) } context 'personal project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.add_master(master) @@ -37,7 +37,7 @@ describe ProjectTeam do context 'group project' do let(:group) { create(:group) } - let!(:project) { create(:empty_project, group: group) } + let!(:project) { create(:project, group: group) } before do group.add_master(master) @@ -75,7 +75,7 @@ describe ProjectTeam do describe '#fetch_members' do context 'personal project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'returns project members' do user = create(:user) @@ -119,7 +119,7 @@ describe ProjectTeam do context 'group project' do let(:group) { create(:group) } - let!(:project) { create(:empty_project, group: group) } + let!(:project) { create(:project, group: group) } it 'returns project members' do group_member = create(:group_member, group: group) @@ -139,7 +139,7 @@ describe ProjectTeam do describe '#find_member' do context 'personal project' do let(:project) do - create(:empty_project, :public, :access_requestable) + create(:project, :public, :access_requestable) end let(:requester) { create(:user) } @@ -160,7 +160,7 @@ describe ProjectTeam do context 'group project' do let(:group) { create(:group, :access_requestable) } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } let(:requester) { create(:user) } before do @@ -182,7 +182,7 @@ describe ProjectTeam do it 'returns Master role' do user = create(:user) group = create(:group) - project = create(:empty_project, namespace: group) + project = create(:project, namespace: group) group.add_master(user) @@ -192,7 +192,7 @@ describe ProjectTeam do it 'returns Owner role' do user = create(:user) group = create(:group) - project = create(:empty_project, namespace: group) + project = create(:project, namespace: group) group.add_owner(user) @@ -205,7 +205,7 @@ describe ProjectTeam do context 'personal project' do let(:project) do - create(:empty_project, :public, :access_requestable) + create(:project, :public, :access_requestable) end context 'when project is not shared with group' do @@ -253,7 +253,7 @@ describe ProjectTeam do context 'group project' do let(:group) { create(:group, :access_requestable) } let!(:project) do - create(:empty_project, group: group) + create(:project, group: group) end before do @@ -277,15 +277,15 @@ describe ProjectTeam do let(:master) { create(:user) } let(:personal_project) do - create(:empty_project, namespace: developer.namespace) + create(:project, namespace: developer.namespace) end let(:group_project) do - create(:empty_project, namespace: group) + create(:project, namespace: group) end - let(:members_project) { create(:empty_project) } - let(:shared_project) { create(:empty_project) } + let(:members_project) { create(:project) } + let(:shared_project) { create(:project) } before do group.add_master(master) diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index c6ceb092810..953df7746eb 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe ProjectWiki do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:repository) { project.repository } let(:user) { project.owner } let(:gitlab_shell) { Gitlab::Shell.new } @@ -15,19 +15,23 @@ describe ProjectWiki do describe "#path_with_namespace" do it "returns the project path with namespace with the .wiki extension" do - expect(subject.path_with_namespace).to eq(project.path_with_namespace + ".wiki") + expect(subject.path_with_namespace).to eq(project.full_path + '.wiki') + end + + it 'returns the same value as #full_path' do + expect(subject.path_with_namespace).to eq(subject.full_path) end end describe '#web_url' do it 'returns the full web URL to the wiki' do - expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/wikis/home") + expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.full_path}/wikis/home") end end describe "#url_to_repo" do it "returns the correct ssh url to the repo" do - expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.path_with_namespace)) + expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.full_path)) end end @@ -38,10 +42,10 @@ describe ProjectWiki do end describe "#http_url_to_repo" do - let(:project) { create :empty_project } + let(:project) { create :project } it 'returns the full http url to the repo' do - expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git" + expected_url = "#{Gitlab.config.gitlab.url}/#{subject.full_path}.git" expect(project_wiki.http_url_to_repo).to eq(expected_url) expect(project_wiki.http_url_to_repo).not_to include('@') @@ -50,7 +54,7 @@ describe ProjectWiki do describe "#wiki_base_path" do it "returns the wiki base path" do - wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.path_with_namespace}/wikis" + wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.full_path}/wikis" expect(subject.wiki_base_path).to eq(wiki_base_path) end @@ -77,7 +81,7 @@ describe ProjectWiki do allow_any_instance_of(Gitlab::Shell).to receive(:add_repository) do create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") end - allow(project).to receive(:path_with_namespace).and_return("non-existant") + allow(project).to receive(:full_path).and_return("non-existant") end describe '#empty?' do @@ -219,7 +223,12 @@ describe ProjectWiki do before do create_page("update-page", "some content") @gollum_page = subject.wiki.paged("update-page") - subject.update_page(@gollum_page, "some other content", :markdown, "updated page") + subject.update_page( + @gollum_page, + content: "some other content", + format: :markdown, + message: "updated page" + ) @page = subject.pages.first.page end @@ -236,7 +245,12 @@ describe ProjectWiki do end it 'updates project activity' do - subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again') + subject.update_page( + @gollum_page, + content: 'Yet more content', + format: :markdown, + message: 'Updated page again' + ) project.reload @@ -269,7 +283,7 @@ describe ProjectWiki do describe '#create_repo!' do it 'creates a repository' do expect(subject).to receive(:init_repo) - .with(subject.path_with_namespace) + .with(subject.full_path) .and_return(true) expect(subject.repository).to receive(:after_create) diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index a54af3bfe59..4c677200ae2 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -163,7 +163,7 @@ describe ProtectedBranch do end context "new project" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'returns false when default_protected_branch is unprotected' do stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 07ed66e127a..62f40c12c0a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' -describe Repository do +describe Repository, models: true do include RepoHelpers TestBlob = Struct.new(:path) let(:project) { create(:project, :repository) } let(:repository) { project.repository } + let(:broken_repository) { create(:project, :broken_storage).repository } let(:user) { create(:user) } let(:commit_options) do @@ -27,12 +28,27 @@ describe Repository do let(:author_email) { 'user@example.org' } let(:author_name) { 'John Doe' } + def expect_to_raise_storage_error + expect { yield }.to raise_error do |exception| + storage_exceptions = [Gitlab::Git::Storage::Inaccessible, Gitlab::Git::CommandError, GRPC::Unavailable] + expect(exception.class).to be_in(storage_exceptions) + end + end + describe '#branch_names_contains' do subject { repository.branch_names_contains(sample_commit.id) } it { is_expected.to include('master') } it { is_expected.not_to include('feature') } it { is_expected.not_to include('fix') } + + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error do + broken_repository.branch_names_contains(sample_commit.id) + end + end + end end describe '#tag_names_contains' do @@ -139,24 +155,60 @@ describe Repository do end describe '#last_commit_for_path' do - subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } + shared_examples 'getting last commit for path' do + subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } + + it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } + + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error do + broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore') + end + end + end + end + + context 'when Gitaly feature last_commit_for_path is enabled' do + it_behaves_like 'getting last commit for path' + end - it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } + context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do + it_behaves_like 'getting last commit for path' + end end describe '#last_commit_id_for_path' do - subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') } + shared_examples 'getting last commit ID for path' do + subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') } + + it "returns last commit id for a given path" do + is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') + end - it "returns last commit id for a given path" do - is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') + it "caches last commit id for a given path" do + cache = repository.send(:cache) + key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}" + + expect(cache).to receive(:fetch).with(key).and_return('c1acaa5') + is_expected.to eq('c1acaa5') + end + + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error do + broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id + end + end + end end - it "caches last commit id for a given path" do - cache = repository.send(:cache) - key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}" + context 'when Gitaly feature last_commit_for_path is enabled' do + it_behaves_like 'getting last commit ID for path' + end - expect(cache).to receive(:fetch).with(key).and_return('c1acaa5') - is_expected.to eq('c1acaa5') + context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do + it_behaves_like 'getting last commit ID for path' end end @@ -182,19 +234,37 @@ describe Repository do end describe '#find_commits_by_message' do - it 'returns commits with messages containing a given string' do - commit_ids = repository.find_commits_by_message('submodule').map(&:id) + shared_examples 'finding commits by message' do + it 'returns commits with messages containing a given string' do + commit_ids = repository.find_commits_by_message('submodule').map(&:id) + + expect(commit_ids).to include( + '5937ac0a7beb003549fc5fd26fc247adbce4a52e', + '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', + 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' + ) + expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + end + + it 'is case insensitive' do + commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end + end - expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') - expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') - expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') - expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + context 'when Gitaly commits_by_message feature is enabled' do + it_behaves_like 'finding commits by message' end - it 'is case insensitive' do - commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + context 'when Gitaly commits_by_message feature is disabled', skip_gitaly_mock: true do + it_behaves_like 'finding commits by message' + end - expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') } + end end end @@ -300,7 +370,7 @@ describe Repository do end context "when committing to another project" do - let(:forked_project) { create(:project) } + let(:forked_project) { create(:project, :repository) } it "creates a fork and commit to the forked project" do expect do @@ -515,12 +585,20 @@ describe Repository do end it 'properly handles query when repo is empty' do - repository = create(:empty_project).repository + repository = create(:project).repository results = repository.search_files_by_content('test', 'master') expect(results).to match_array([]) end + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error do + broken_repository.search_files_by_content('feature', 'master') + end + end + end + describe 'result' do subject { results.first } @@ -543,12 +621,28 @@ describe Repository do end it 'properly handles query when repo is empty' do - repository = create(:empty_project).repository + repository = create(:project).repository results = repository.search_files_by_name('test', 'master') expect(results).to match_array([]) end + + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') } + end + end + end + + describe '#fetch_ref' do + describe 'when storage is broken', broken_storage: true do + it 'should raise a storage error' do + path = broken_repository.path_to_repo + + expect_to_raise_storage_error { broken_repository.fetch_ref(path, '1', '2') } + end + end end describe '#create_ref' do @@ -942,7 +1036,7 @@ describe Repository do end it 'expires creation and branch cache' do - empty_repository = create(:empty_project, :empty_repo).repository + empty_repository = create(:project, :empty_repo).repository expect(empty_repository).to receive(:expire_exists_cache) expect(empty_repository).to receive(:expire_root_ref_cache) @@ -962,10 +1056,16 @@ describe Repository do end it 'returns false if no full path can be constructed' do - allow(repository).to receive(:path_with_namespace).and_return(nil) + allow(repository).to receive(:full_path).and_return(nil) expect(repository.exists?).to eq(false) end + + context 'with broken storage', broken_storage: true do + it 'should raise a storage error' do + expect_to_raise_storage_error { broken_repository.exists? } + end + end end describe '#exists?' do @@ -1124,7 +1224,7 @@ describe Repository do end describe 'skip_merges option' do - subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } } + subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map { |k| k.id } } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } end @@ -1804,7 +1904,7 @@ describe Repository do end describe '#commit_count_for_ref' do - let(:project) { create :empty_project } + let(:project) { create :project } context 'with a non-existing repository' do it 'returns 0' do diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 8b6b02916ae..8f05deb8b15 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -21,7 +21,7 @@ describe SentNotification do end context "when the noteable project and discussion project match" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:issue) { create(:issue, project: project) } let(:discussion_id) { create(:note, project: project, noteable: issue).discussion_id } subject { build(:sent_notification, project: project, noteable: issue, in_reply_to_discussion_id: discussion_id) } @@ -128,7 +128,7 @@ describe SentNotification do end context 'for commit' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit } subject { described_class.record(commit, project.creator.id) } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index dba9fc4ac9b..0f2f906c667 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -23,7 +23,7 @@ describe Service do end context 'when repository is empty' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'returns true' do expect(service.can_test?).to be true @@ -46,7 +46,7 @@ describe Service do end context 'when repository is empty' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'test runs execute' do expect(service).to receive(:execute).with(data) @@ -69,7 +69,7 @@ describe Service do api_key: '123456789' }) end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe 'is prefilled for projects pushover service' do it "has all fields prefilled" do @@ -88,7 +88,7 @@ describe Service do describe "{property}_changed?" do let(:service) do BambooService.create( - project: create(:empty_project), + project: create(:project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -128,7 +128,7 @@ describe Service do describe "{property}_touched?" do let(:service) do BambooService.create( - project: create(:empty_project), + project: create(:project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -168,7 +168,7 @@ describe Service do describe "{property}_was" do let(:service) do BambooService.create( - project: create(:empty_project), + project: create(:project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -208,7 +208,7 @@ describe Service do describe 'initialize service with no properties' do let(:service) do GitlabIssueTrackerService.create( - project: create(:empty_project), + project: create(:project), title: 'random title' ) end @@ -223,7 +223,7 @@ describe Service do end describe "callbacks" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:service) do RedmineService.new( project: project, diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 904bf2c6144..de3ca300ae3 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -33,7 +33,7 @@ describe Snippet do describe '#to_reference' do context 'when snippet belongs to a project' do - let(:project) { build(:empty_project, name: 'sample-project') } + let(:project) { build(:project, name: 'sample-project') } let(:snippet) { build(:snippet, id: 1, project: project) } it 'returns a String reference to the object' do @@ -41,7 +41,7 @@ describe Snippet do end it 'supports a cross-project reference' do - another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, name: 'another-project', namespace: project.namespace) expect(snippet.to_reference(another_project)).to eq "sample-project$1" end end @@ -54,14 +54,14 @@ describe Snippet do end it 'still returns shortest reference when project arg present' do - another_project = build(:empty_project, name: 'another-project') + another_project = build(:project, name: 'another-project') expect(snippet.to_reference(another_project)).to eq "$1" end end end describe '#file_name' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'file_name is nil' do let(:snippet) { create(:snippet, project: project, file_name: nil) } @@ -132,7 +132,7 @@ describe Snippet do end describe '#participants' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } let!(:note1) do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb index cc28c6d4004..3b5e7ca0d39 100644 --- a/spec/models/trending_project_spec.rb +++ b/spec/models/trending_project_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' describe TrendingProject do let(:user) { create(:user) } - let(:public_project1) { create(:empty_project, :public) } - let(:public_project2) { create(:empty_project, :public) } - let(:public_project3) { create(:empty_project, :public) } - let(:private_project) { create(:empty_project, :private) } - let(:internal_project) { create(:empty_project, :internal) } + let(:public_project1) { create(:project, :public) } + let(:public_project2) { create(:project, :public) } + let(:public_project3) { create(:project, :public) } + let(:private_project) { create(:project, :private) } + let(:internal_project) { create(:project, :internal) } before do 3.times do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ec98a3f3498..6c8248eeb40 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -80,7 +80,7 @@ describe User do describe '#project_members' do it 'does not include project memberships for which user is a requester' do user = create(:user) - project = create(:empty_project, :public, :access_requestable) + project = create(:project, :public, :access_requestable) project.request_access(user) expect(user.project_members).to be_empty @@ -118,6 +118,17 @@ describe User do expect(user).to validate_uniqueness_of(:username).case_insensitive end + + context 'when username is changed' do + let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) } + + it 'validates move_dir is allowed for the namespace' do + expect(user.namespace).to receive(:any_project_has_container_registry_tags?).and_return(true) + user.username = 'new_path' + expect(user).to be_invalid + expect(user.errors.messages[:username].first).to match('cannot be changed if a personal project has container registry tags') + end + end end it { is_expected.to validate_presence_of(:projects_limit) } @@ -560,11 +571,11 @@ describe User do before do @user = create(:user) - @project = create(:empty_project, namespace: @user.namespace) - @project_2 = create(:empty_project, group: create(:group)) do |project| + @project = create(:project, namespace: @user.namespace) + @project_2 = create(:project, group: create(:group)) do |project| project.add_master(@user) end - @project_3 = create(:empty_project, group: create(:group)) do |project| + @project_3 = create(:project, group: create(:group)) do |project| project.add_developer(@user) end end @@ -609,7 +620,7 @@ describe User do describe 'namespaced' do before do @user = create :user - @project = create(:empty_project, namespace: @user.namespace) + @project = create(:project, namespace: @user.namespace) end it { expect(@user.several_namespaces?).to be_falsey } @@ -666,7 +677,7 @@ describe User do end describe '.without_projects' do - let!(:project) { create(:empty_project, :public, :access_requestable) } + let!(:project) { create(:project, :public, :access_requestable) } let!(:user) { create(:user) } let!(:user_without_project) { create(:user) } let!(:user_without_project2) { create(:user) } @@ -1196,8 +1207,8 @@ describe User do describe '#starred?' do it 'determines if user starred a project' do user = create :user - project1 = create(:empty_project, :public) - project2 = create(:empty_project, :public) + project1 = create(:project, :public) + project2 = create(:project, :public) expect(user.starred?(project1)).to be_falsey expect(user.starred?(project2)).to be_falsey @@ -1223,7 +1234,7 @@ describe User do describe '#toggle_star' do it 'toggles stars' do user = create :user - project = create(:empty_project, :public) + project = create(:project, :public) expect(user.starred?(project)).to be_falsey user.toggle_star(project) @@ -1276,9 +1287,9 @@ describe User do describe "#contributed_projects" do subject { create(:user) } - let!(:project1) { create(:empty_project) } - let!(:project2) { create(:empty_project, forked_from_project: project3) } - let!(:project3) { create(:empty_project) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project, forked_from_project: project3) } + let!(:project3) { create(:project) } let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } let!(:push_event) { create(:event, :pushed, project: project1, target: project1, author: subject) } let!(:merge_event) { create(:event, :created, project: project3, target: merge_request, author: subject) } @@ -1376,7 +1387,7 @@ describe User do context 'with a minimum access level' do it 'includes projects for which the user is an owner' do user = create(:user) - project = create(:empty_project, :private, namespace: user.namespace) + project = create(:project, :private, namespace: user.namespace) expect(user.authorized_projects(Gitlab::Access::REPORTER)) .to contain_exactly(project) @@ -1384,7 +1395,7 @@ describe User do it 'includes projects for which the user is a master' do user = create(:user) - project = create(:empty_project, :private) + project = create(:project, :private) project.team << [user, Gitlab::Access::MASTER] @@ -1395,7 +1406,7 @@ describe User do it "includes user's personal projects" do user = create(:user) - project = create(:empty_project, :private, namespace: user.namespace) + project = create(:project, :private, namespace: user.namespace) expect(user.authorized_projects).to include(project) end @@ -1403,7 +1414,7 @@ describe User do it "includes personal projects user has been given access to" do user1 = create(:user) user2 = create(:user) - project = create(:empty_project, :private, namespace: user1.namespace) + project = create(:project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] @@ -1412,7 +1423,7 @@ describe User do it "includes projects of groups user has been added to" do group = create(:group) - project = create(:empty_project, group: group) + project = create(:project, group: group) user = create(:user) group.add_developer(user) @@ -1422,7 +1433,7 @@ describe User do it "does not include projects of groups user has been removed from" do group = create(:group) - project = create(:empty_project, group: group) + project = create(:project, group: group) user = create(:user) member = group.add_developer(user) @@ -1434,7 +1445,7 @@ describe User do it "includes projects shared with user's group" do user = create(:user) - project = create(:empty_project, :private) + project = create(:project, :private) group = create(:group) group.add_reporter(user) @@ -1446,7 +1457,7 @@ describe User do it "does not include destroyed projects user had access to" do user1 = create(:user) user2 = create(:user) - project = create(:empty_project, :private, namespace: user1.namespace) + project = create(:project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] expect(user2.authorized_projects).to include(project) @@ -1457,7 +1468,7 @@ describe User do it "does not include projects of destroyed groups user had access to" do group = create(:group) - project = create(:empty_project, namespace: group) + project = create(:project, namespace: group) user = create(:user) group.add_developer(user) @@ -1472,9 +1483,9 @@ describe User do let(:user) { create(:user) } it 'includes projects for which the user access level is above or equal to reporter' do - reporter_project = create(:empty_project) { |p| p.add_reporter(user) } - developer_project = create(:empty_project) { |p| p.add_developer(user) } - master_project = create(:empty_project) { |p| p.add_master(user) } + reporter_project = create(:project) { |p| p.add_reporter(user) } + developer_project = create(:project) { |p| p.add_developer(user) } + master_project = create(:project) { |p| p.add_master(user) } expect(user.projects_where_can_admin_issues.to_a).to eq([master_project, developer_project, reporter_project]) expect(user.can?(:admin_issue, master_project)).to eq(true) @@ -1483,8 +1494,8 @@ describe User do end it 'does not include for which the user access level is below reporter' do - project = create(:empty_project) - guest_project = create(:empty_project) { |p| p.add_guest(user) } + project = create(:project) + guest_project = create(:project) { |p| p.add_guest(user) } expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, guest_project)).to eq(false) @@ -1492,14 +1503,14 @@ describe User do end it 'does not include archived projects' do - project = create(:empty_project, :archived) + project = create(:project, :archived) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) end it 'does not include projects for which issues are disabled' do - project = create(:empty_project, :issues_disabled) + project = create(:project, :issues_disabled) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) @@ -1515,7 +1526,7 @@ describe User do end context 'without any projects' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'does not load' do expect(user.ci_authorized_runners).to be_empty @@ -1524,7 +1535,7 @@ describe User do context 'with personal projects runners' do let(:namespace) { create(:namespace, owner: user) } - let(:project) { create(:empty_project, namespace: namespace) } + let(:project) { create(:project, namespace: namespace) } it 'loads' do expect(user.ci_authorized_runners).to contain_exactly(runner) @@ -1555,7 +1566,7 @@ describe User do context 'with groups projects runners' do let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } + let(:project) { create(:project, group: group) } def add_user(access) group.add_user(user, access) @@ -1565,7 +1576,7 @@ describe User do end context 'with other projects runners' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } def add_user(access) project.team << [user, access] @@ -1576,8 +1587,8 @@ describe User do end describe '#projects_with_reporter_access_limited_to' do - let(:project1) { create(:empty_project) } - let(:project2) { create(:empty_project) } + let(:project1) { create(:project) } + let(:project2) { create(:project) } let(:user) { create(:user) } before do @@ -1719,8 +1730,8 @@ describe User do end describe '#refresh_authorized_projects', clean_gitlab_redis_shared_state: true do - let(:project1) { create(:empty_project) } - let(:project2) { create(:empty_project) } + let(:project1) { create(:project) } + let(:project2) { create(:project) } let(:user) { create(:user) } before do @@ -1976,4 +1987,28 @@ describe User do expect(user.allow_password_authentication?).to be_falsey end end + + describe '#personal_projects_count' do + it 'returns the number of personal projects using a single query' do + user = build(:user) + projects = double(:projects, count: 1) + + expect(user).to receive(:personal_projects).once.and_return(projects) + + 2.times do + expect(user.personal_projects_count).to eq(1) + end + end + end + + describe '#projects_limit_left' do + it 'returns the number of projects that can be created by the user' do + user = build(:user) + + allow(user).to receive(:projects_limit).and_return(10) + allow(user).to receive(:personal_projects_count).and_return(5) + + expect(user.projects_limit_left).to eq(5) + end + end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 55d9c4ddd7b..40a222be24d 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe WikiPage do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { project.owner } let(:wiki) { ProjectWiki.new(project, user) } @@ -178,12 +178,12 @@ describe WikiPage do end it "updates the content of the page" do - @page.update("new content") + @page.update(content: "new content") @page = wiki.find_page(title) end it "returns true" do - expect(@page.update("more content")).to be_truthy + expect(@page.update(content: "more content")).to be_truthy end end end @@ -195,29 +195,42 @@ describe WikiPage do end after do - destroy_page("Update") + destroy_page(@page.title) end context "with valid attributes" do it "updates the content of the page" do - @page.update("new content") + new_content = "new content" + + @page.update(content: new_content) @page = wiki.find_page("Update") + + expect(@page.content).to eq("new content") + end + + it "updates the title of the page" do + new_title = "Index v.1.2.4" + + @page.update(title: new_title) + @page = wiki.find_page(new_title) + + expect(@page.title).to eq(new_title) end it "returns true" do - expect(@page.update("more content")).to be_truthy + expect(@page.update(content: "more content")).to be_truthy end end context 'with same last commit sha' do it 'returns true' do - expect(@page.update('more content', last_commit_sha: @page.last_commit_sha)).to be_truthy + expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy end end context 'with different last commit sha' do it 'raises exception' do - expect { @page.update('more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError) + expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError) end end end @@ -249,7 +262,7 @@ describe WikiPage do end it "returns an array of all commits for the page" do - 3.times { |i| @page.update("content #{i}") } + 3.times { |i| @page.update(content: "content #{i}") } expect(@page.versions.count).to eq(4) end end @@ -294,7 +307,7 @@ describe WikiPage do before do create_page('Update', 'content') @page = wiki.find_page('Update') - 3.times { |i| @page.update("content #{i}") } + 3.times { |i| @page.update(content: "content #{i}") } end after do @@ -338,7 +351,7 @@ describe WikiPage do end it 'returns false for updated wiki page' do - updated_wiki_page = original_wiki_page.update("Updated content") + updated_wiki_page = original_wiki_page.update(content: "Updated content") expect(original_wiki_page).not_to eq(updated_wiki_page) end end @@ -360,7 +373,7 @@ describe WikiPage do it 'is changed after page updated' do last_commit_sha_before_update = @page.last_commit_sha - @page.update("new content") + @page.update(content: "new content") @page = wiki.find_page("Update") expect(@page.last_commit_sha).not_to eq last_commit_sha_before_update diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index a83a83a7349..8e1bc3d1543 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -17,7 +17,7 @@ describe Ci::BuildPolicy do describe '#rules' do context 'when user does not have access to the project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } context 'when public builds are enabled' do it 'does not include ability to read build' do @@ -35,7 +35,7 @@ describe Ci::BuildPolicy do end context 'when anonymous user has access to the project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'when public builds are enabled' do it 'includes ability to read build' do @@ -53,7 +53,7 @@ describe Ci::BuildPolicy do end context 'when team member has access to the project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'team member is a guest' do before do @@ -97,7 +97,7 @@ describe Ci::BuildPolicy do end describe 'rules for protected ref' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, ref: 'some-ref', pipeline: pipeline) } before do diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb index b11b06d301f..48a8064c5fc 100644 --- a/spec/policies/ci/pipeline_policy_spec.rb +++ b/spec/policies/ci/pipeline_policy_spec.rb @@ -10,7 +10,7 @@ describe Ci::PipelinePolicy, :models do describe 'rules' do describe 'rules for protected ref' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.add_developer(user) diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb index 3d3e3d3755b..be40dbb2aa9 100644 --- a/spec/policies/ci/trigger_policy_spec.rb +++ b/spec/policies/ci/trigger_policy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::TriggerPolicy do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:trigger) { create(:ci_trigger, project: project, owner: owner) } let(:policies) do diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb index 035e20c7452..de4cb5b30c5 100644 --- a/spec/policies/environment_policy_spec.rb +++ b/spec/policies/environment_policy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe EnvironmentPolicy do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) do create(:environment, :with_review_app, project: project) @@ -14,7 +14,7 @@ describe EnvironmentPolicy do describe '#rules' do context 'when user does not have access to the project' do - let(:project) { create(:project, :private) } + let(:project) { create(:project, :private, :repository) } it 'does not include ability to stop environment' do expect(policy).to be_disallowed :stop_environment @@ -22,7 +22,7 @@ describe EnvironmentPolicy do end context 'when anonymous user has access to the project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it 'does not include ability to stop environment' do expect(policy).to be_disallowed :stop_environment @@ -30,7 +30,7 @@ describe EnvironmentPolicy do end context 'when team member has access to the project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } before do project.add_developer(user) diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index 279b96fb2af..be4c24c727c 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -14,7 +14,7 @@ describe IssuePolicy do context 'a private project' do let(:non_member) { create(:user) } - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) } let(:issue_no_assignee) { create(:issue, project: project) } @@ -109,7 +109,7 @@ describe IssuePolicy do end context 'a public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) } let(:issue_no_assignee) { create(:issue, project: project) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 1f51ced1beb..4dbaf7fb025 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -7,7 +7,7 @@ describe ProjectPolicy do let(:master) { create(:user) } let(:owner) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, :public, namespace: owner.namespace) } + let(:project) { create(:project, :public, namespace: owner.namespace) } let(:guest_permissions) do %i[ @@ -82,7 +82,7 @@ describe ProjectPolicy do end it 'does not include the read_issue permission when the issue author is not a member of the private project' do - project = create(:empty_project, :private) + project = create(:project, :private) issue = create(:issue, project: project) user = issue.author @@ -129,7 +129,7 @@ describe ProjectPolicy do context 'when a project has pending invites, and the current user is anonymous' do let(:group) { create(:group, :public) } - let(:project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] } let(:anonymous_permissions) { guest_permissions - user_permissions } @@ -146,7 +146,7 @@ describe ProjectPolicy do end context 'abilities for non-public projects' do - let(:project) { create(:empty_project, namespace: owner.namespace) } + let(:project) { create(:project, namespace: owner.namespace) } subject { described_class.new(current_user, project) } diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index bae35ee31c6..f0bf46c480a 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ProjectSnippetPolicy do let(:regular_user) { create(:user) } let(:external_user) { create(:user, :external) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:author_permissions) do [ diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index f05d5c7fce5..a7a34ecac72 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::BuildPresenter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } @@ -85,7 +85,7 @@ describe Ci::BuildPresenter do describe 'quack like a Ci::Build permission-wise' do context 'user is not allowed' do - let(:project) { create(:empty_project, public_builds: false) } + let(:project) { create(:project, public_builds: false) } it 'returns false' do expect(presenter.can?(nil, :read_build)).to be_falsy @@ -93,7 +93,7 @@ describe Ci::BuildPresenter do end context 'user is allowed' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } it 'returns true' do expect(presenter.can?(nil, :read_build)).to be_truthy diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb index 9134d1cc31c..e4886a8f019 100644 --- a/spec/presenters/ci/pipeline_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::PipelinePresenter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } subject(:presenter) do diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb index 9e6aae7bcad..db62f86edb0 100644 --- a/spec/presenters/ci/variable_presenter_spec.rb +++ b/spec/presenters/ci/variable_presenter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::VariablePresenter do include Gitlab::Routing.url_helpers - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:variable) { create(:ci_variable, project: project) } subject(:presenter) do diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb index 1e015c71f5b..81eb05a9a6b 100644 --- a/spec/presenters/conversational_development_index/metric_presenter_spec.rb +++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb @@ -8,9 +8,9 @@ describe ConversationalDevelopmentIndex::MetricPresenter do it 'includes instance score, leader score and percentage score' do issues_card = subject.cards.first - expect(issues_card.instance_score).to eq 1.234 - expect(issues_card.leader_score).to eq 9.256 - expect(issues_card.percentage_score).to be_within(0.1).of(13.3) + expect(issues_card.instance_score).to eq(1.234) + expect(issues_card.leader_score).to eq(9.256) + expect(issues_card.percentage_score).to eq(13.331) end end diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index c1a0313b13c..2187be0190d 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequestPresenter do let(:resource) { create :merge_request, source_project: project } - let(:project) { create :empty_project } + let(:project) { create :project } let(:user) { create(:user) } describe '#ci_status' do @@ -71,7 +71,7 @@ describe MergeRequestPresenter do end describe '#conflict_resolution_path' do - let(:project) { create :empty_project } + let(:project) { create :project } let(:user) { create :user } let(:presenter) { described_class.new(resource, current_user: user) } let(:path) { presenter.conflict_resolution_path } @@ -105,7 +105,7 @@ describe MergeRequestPresenter do end context 'issues links' do - let(:project) { create(:project, :private, creator: user, namespace: user.namespace) } + let(:project) { create(:project, :private, :repository, creator: user, namespace: user.namespace) } let(:issue_a) { create(:issue, project: project) } let(:issue_b) { create(:issue, project: project) } diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb index 5c39e1b5f96..2cc0076d695 100644 --- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb +++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::Settings::DeployKeysPresenter do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:deploy_key) { create(:deploy_key, public: true) } diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index c8eacb38e6f..6bd17697c33 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -7,7 +7,7 @@ describe API::AccessRequests do let(:stranger) { create(:user) } let(:project) do - create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 6d822b5cb4f..1dd9f3f6ddc 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::AwardEmoji do let(:user) { create(:user) } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index c27db716ef8..43b381c2219 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -6,7 +6,7 @@ describe API::Boards do let(:non_member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:dev_label) do create(:label, title: 'Development', color: '#FFAABB', project: project) @@ -188,7 +188,7 @@ describe API::Boards do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:empty_project, namespace: owner.namespace) } + let(:project) { create(:project, namespace: owner.namespace) } it "deletes the list if an admin requests it" do delete api("#{base_url}/#{dev_list.id}", owner) diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb new file mode 100644 index 00000000000..76521e55994 --- /dev/null +++ b/spec/requests/api/circuit_breakers_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe API::CircuitBreakers do + let(:user) { create(:user) } + let(:admin) { create(:admin) } + + describe 'GET circuit_breakers/repository_storage' do + it 'returns a 401 for anonymous users' do + get api('/circuit_breakers/repository_storage') + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + get api('/circuit_breakers/repository_storage', user) + + expect(response).to have_http_status(403) + end + + it 'returns an Array of storages' do + expect(Gitlab::Git::Storage::Health).to receive(:for_all_storages) do + [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])] + end + + get api('/circuit_breakers/repository_storage', admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_kind_of(Array) + expect(json_response.first['storage_name']).to eq('broken') + expect(json_response.first['failing_on_hosts']).to eq(['web01']) + expect(json_response.first['total_failures']).to eq(4) + end + + describe 'GET circuit_breakers/repository_storage/failing' do + it 'returns an array of failing storages' do + expect(Gitlab::Git::Storage::Health).to receive(:for_failing_storages) do + [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])] + end + + get api('/circuit_breakers/repository_storage/failing', admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_kind_of(Array) + end + end + end + + describe 'DELETE circuit_breakers/repository_storage' do + it 'clears all circuit_breakers' do + expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!) + + delete api('/circuit_breakers/repository_storage', admin) + + expect(response).to have_http_status(204) + end + end +end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 8b62aa268d9..3c02e6302b4 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -44,8 +44,8 @@ describe API::CommitStatuses do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id) - json_response.sort_by!{ |status| status['id'] } - expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false]) + json_response.sort_by! { |status| status['id'] } + expect(json_response.map { |status| status['allow_failure'] }).to eq([true, false, false, false]) end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 0dad547735d..992a6e8d76a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -3,29 +3,27 @@ require 'mime/types' describe API::Commits do let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } - let!(:guest) { create(:project_member, :guest, user: user2, project: project) } - let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } - let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') } + let(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + let(:branch_with_dot) { project.repository.find_branch('ends-with.json') } + let(:branch_with_slash) { project.repository.find_branch('improve/awesome') } + + let(:project_id) { project.id } + let(:current_user) { nil } before do - project.team << [user, :reporter] + project.add_master(user) end - describe "List repository commits" do - context "authorized user" do - before do - project.team << [user2, :reporter] - end - + describe 'GET /projects/:id/repository/commits' do + context 'authorized user' do it "returns project commits" do commit = project.repository.commit - get api("/projects/#{project.id}/repository/commits", user) + get api("/projects/#{project_id}/repository/commits", user) expect(response).to have_http_status(200) - expect(json_response).to be_an Array + expect(response).to match_response_schema('public_api/v4/commits') expect(json_response.first['id']).to eq(commit.id) expect(json_response.first['committer_name']).to eq(commit.committer_name) expect(json_response.first['committer_email']).to eq(commit.committer_email) @@ -34,7 +32,7 @@ describe API::Commits do it 'include correct pagination headers' do commit_count = project.repository.count_commits(ref: 'master').to_s - get api("/projects/#{project.id}/repository/commits", user) + get api("/projects/#{project_id}/repository/commits", user) expect(response).to include_pagination_headers expect(response.headers['X-Total']).to eq(commit_count) @@ -44,8 +42,9 @@ describe API::Commits do context "unauthorized user" do it "does not return project commits" do - get api("/projects/#{project.id}/repository/commits") - expect(response).to have_http_status(401) + get api("/projects/#{project_id}/repository/commits") + + expect(response).to have_http_status(404) end end @@ -54,7 +53,7 @@ describe API::Commits do commits = project.repository.commits("master") after = commits.second.created_at - get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user) + get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user) expect(json_response.size).to eq 2 expect(json_response.first["id"]).to eq(commits.first.id) @@ -66,7 +65,7 @@ describe API::Commits do after = commits.second.created_at commit_count = project.repository.count_commits(ref: 'master', after: after).to_s - get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user) + get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user) expect(response).to include_pagination_headers expect(response.headers['X-Total']).to eq(commit_count) @@ -79,7 +78,7 @@ describe API::Commits do commits = project.repository.commits("master") before = commits.second.created_at - get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) + get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user) if commits.size >= 20 expect(json_response.size).to eq(20) @@ -96,7 +95,7 @@ describe API::Commits do before = commits.second.created_at commit_count = project.repository.count_commits(ref: 'master', before: before).to_s - get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) + get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user) expect(response).to include_pagination_headers expect(response.headers['X-Total']).to eq(commit_count) @@ -106,7 +105,7 @@ describe API::Commits do context "invalid xmlschema date parameters" do it "returns an invalid parameter error message" do - get api("/projects/#{project.id}/repository/commits?since=invalid-date", user) + get api("/projects/#{project_id}/repository/commits?since=invalid-date", user) expect(response).to have_http_status(400) expect(json_response['error']).to eq('since is invalid') @@ -118,7 +117,7 @@ describe API::Commits do path = 'files/ruby/popen.rb' commit_count = project.repository.count_commits(ref: 'master', path: path).to_s - get api("/projects/#{project.id}/repository/commits?path=#{path}", user) + get api("/projects/#{project_id}/repository/commits?path=#{path}", user) expect(json_response.size).to eq(3) expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") @@ -130,7 +129,7 @@ describe API::Commits do path = 'files/ruby/popen.rb' commit_count = project.repository.count_commits(ref: 'master', path: path).to_s - get api("/projects/#{project.id}/repository/commits?path=#{path}", user) + get api("/projects/#{project_id}/repository/commits?path=#{path}", user) expect(response).to include_pagination_headers expect(response.headers['X-Total']).to eq(commit_count) @@ -143,7 +142,7 @@ describe API::Commits do let(:per_page) { 5 } let(:ref_name) { 'master' } let!(:request) do - get api("/projects/#{project.id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user) + get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user) end it 'returns correct headers' do @@ -181,10 +180,10 @@ describe API::Commits do end describe "POST /projects/:id/repository/commits" do - let!(:url) { "/projects/#{project.id}/repository/commits" } + let!(:url) { "/projects/#{project_id}/repository/commits" } it 'returns a 403 unauthorized for user without permissions' do - post api(url, user2) + post api(url, guest) expect(response).to have_http_status(403) end @@ -227,7 +226,7 @@ describe API::Commits do it 'a new file in project repo' do post api(url, user), valid_c_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) expect(json_response['committer_name']).to eq(user.name) expect(json_response['committer_email']).to eq(user.email) @@ -453,13 +452,17 @@ describe API::Commits do end end - describe "Get a single commit" do - context "authorized user" do - it "returns a commit by sha" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + describe 'GET /projects/:id/repository/commits/:sha' do + let(:commit) { project.repository.commit } + let(:commit_id) { commit.id } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" } - expect(response).to have_http_status(200) - commit = project.repository.commit + shared_examples_for 'ref commit' do + it 'returns the ref last commit' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/commit/detail') expect(json_response['id']).to eq(commit.id) expect(json_response['short_id']).to eq(commit.short_id) expect(json_response['title']).to eq(commit.title) @@ -474,222 +477,539 @@ describe API::Commits do expect(json_response['stats']['additions']).to eq(commit.stats.additions) expect(json_response['stats']['deletions']).to eq(commit.stats.deletions) expect(json_response['stats']['total']).to eq(commit.stats.total) + expect(json_response['status']).to be_nil end - it "returns a 404 error if not found" do - get api("/projects/#{project.id}/repository/commits/invalid_sha", user) - expect(response).to have_http_status(404) + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Commit Not Found' } + end end - it "returns nil for commit without CI" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(200) - expect(json_response['status']).to be_nil + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end end + end - it "returns status for CI" do - pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha) - pipeline.update(status: 'success') + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + it_behaves_like 'ref commit' + end - expect(response).to have_http_status(200) - expect(json_response['status']).to eq(pipeline.status) + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } end + end - it "returns status for CI when pipeline is created" do - project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha) + context 'when authenticated', 'as a master' do + let(:current_user) { user } - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + it_behaves_like 'ref commit' - expect(response).to have_http_status(200) - expect(json_response['status']).to eq("created") + context 'when branch contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref commit' end - end - context "unauthorized user" do - it "does not return the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - expect(response).to have_http_status(401) + context 'when branch contains a slash' do + let(:commit_id) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + end + end + + context 'when branch contains an escaped slash' do + let(:commit) { project.repository.commit(branch_with_slash.name) } + let(:commit_id) { CGI.escape(branch_with_slash.name) } + + it_behaves_like 'ref commit' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'ref commit' + + context 'when branch contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref commit' + end + end + + context 'when the ref has a pipeline' do + let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha) } + + it 'includes a "created" status' do + get api(route, current_user) + + expect(response).to have_http_status(200) + expect(response).to match_response_schema('public_api/v4/commit/detail') + expect(json_response['status']).to eq('created') + end + + context 'when pipeline succeeds' do + before do + pipeline.update(status: 'success') + end + + it 'includes a "success" status' do + get api(route, current_user) + + expect(response).to have_http_status(200) + expect(response).to match_response_schema('public_api/v4/commit/detail') + expect(json_response['status']).to eq('success') + end + end end end end - describe "Get the diff of a commit" do - context "authorized user" do - before do - project.team << [user2, :reporter] + describe 'GET /projects/:id/repository/commits/:sha/diff' do + let(:commit) { project.repository.commit } + let(:commit_id) { commit.id } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/diff" } + + shared_examples_for 'ref diff' do + it 'returns the diff of the selected commit' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be >= 1 + expect(json_response.first.keys).to include 'diff' end - it "returns the diff of the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - expect(response).to have_http_status(200) + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } - expect(json_response).to be_an Array - expect(json_response.length).to be >= 1 - expect(json_response.first.keys).to include "diff" + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Commit Not Found' } + end end - it "returns a 404 error if invalid commit" do - get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - expect(response).to have_http_status(404) + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end end end - context "unauthorized user" do - it "does not return the diff of the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - expect(response).to have_http_status(401) + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'ref diff' + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'ref diff' + + context 'when branch contains a dot' do + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref diff' + end + + context 'when branch contains a slash' do + let(:commit_id) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + end + end + + context 'when branch contains an escaped slash' do + let(:commit_id) { CGI.escape(branch_with_slash.name) } + + it_behaves_like 'ref diff' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'ref diff' + + context 'when branch contains a dot' do + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref diff' + end end end end - describe 'Get the comments of a commit' do - context 'authorized user' do - it 'returns merge_request comments' do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['note']).to eq('a comment on a commit') - expect(json_response.first['author']['id']).to eq(user.id) + describe 'GET /projects/:id/repository/commits/:sha/comments' do + let(:commit) { project.repository.commit } + let(:commit_id) { commit.id } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/comments" } + + shared_examples_for 'ref comments' do + context 'when ref exists' do + before do + create(:note_on_commit, author: user, project: project, commit_id: commit.id, note: 'a comment on a commit') + create(:note_on_commit, author: user, project: project, commit_id: commit.id, note: 'another comment on a commit') + end + + it 'returns the diff of the selected commit' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/commit_notes') + expect(json_response.size).to eq(2) + expect(json_response.first['note']).to eq('a comment on a commit') + expect(json_response.first['author']['id']).to eq(user.id) + end end - it 'returns a 404 error if merge_request_id not found' do - get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) - expect(response).to have_http_status(404) + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Commit Not Found' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end + end + end + + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'ref comments' + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } end end - context 'unauthorized user' do - it 'does not return the diff of the selected commit' do - get api("/projects/#{project.id}/repository/commits/1234ab/comments") - expect(response).to have_http_status(401) + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'ref comments' + + context 'when branch contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref comments' + end + + context 'when branch contains a slash' do + let(:commit) { project.repository.commit(branch_with_slash.name) } + let(:commit_id) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + end + end + + context 'when branch contains an escaped slash' do + let(:commit) { project.repository.commit(branch_with_slash.name) } + let(:commit_id) { CGI.escape(branch_with_slash.name) } + + it_behaves_like 'ref comments' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'ref comments' + + context 'when branch contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref comments' + end end end context 'when the commit is present on two projects' do - let(:forked_project) { create(:project, :repository, creator: user2, namespace: user2.namespace) } - let!(:forked_project_note) { create(:note_on_commit, author: user2, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') } + let(:forked_project) { create(:project, :repository, creator: guest, namespace: guest.namespace) } + let!(:forked_project_note) { create(:note_on_commit, author: guest, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') } + let(:project_id) { forked_project.id } + let(:commit_id) { forked_project.repository.commit.id } it 'returns the comments for the target project' do - get api("/projects/#{forked_project.id}/repository/commits/#{forked_project.repository.commit.id}/comments", user2) + get api(route, guest) - expect(response).to have_http_status(200) - expect(json_response.length).to eq(1) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/commit_notes') + expect(json_response.size).to eq(1) expect(json_response.first['note']).to eq('a comment on a commit for fork') - expect(json_response.first['author']['id']).to eq(user2.id) + expect(json_response.first['author']['id']).to eq(guest.id) end end end describe 'POST :id/repository/commits/:sha/cherry_pick' do - let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } + let(:commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } + let(:commit_id) { commit.id } + let(:branch) { 'master' } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/cherry_pick" } + + shared_examples_for 'ref cherry-pick' do + context 'when ref exists' do + it 'cherry-picks the ref commit' do + post api(route, current_user), branch: branch + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/commit/basic') + expect(json_response['title']).to eq(commit.title) + expect(json_response['message']).to eq(commit.message) + expect(json_response['author_name']).to eq(commit.author_name) + expect(json_response['committer_name']).to eq(user.name) + end + end - context 'authorized user' do - it 'cherry picks a commit' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(201) - expect(json_response['title']).to eq(master_pickable_commit.title) - expect(json_response['message']).to eq(master_pickable_commit.message) - expect(json_response['author_name']).to eq(master_pickable_commit.author_name) - expect(json_response['committer_name']).to eq(user.name) + it_behaves_like '403 response' do + let(:request) { post api(route, current_user), branch: 'master' } + end end + end - it 'returns 400 if commit is already included in the target branch' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } - expect(response).to have_http_status(400) - expect(json_response['message']).to include('Sorry, we cannot cherry-pick this commit automatically.') + it_behaves_like '403 response' do + let(:request) { post api(route), branch: 'master' } + end + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { post api(route), branch: 'master' } + let(:message) { '404 Project Not Found' } end + end - it 'returns 400 if you are not allowed to push to the target branch' do - project.team << [user2, :developer] - protected_branch = create(:protected_branch, project: project, name: 'feature') + context 'when authenticated', 'as an owner' do + let(:current_user) { user } - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name + it_behaves_like 'ref cherry-pick' - expect(response).to have_http_status(400) - expect(json_response['message']).to eq('You are not allowed to push into this branch') + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), branch: 'master' } + let(:message) { '404 Commit Not Found' } + end + end + + context 'when branch is missing' do + it_behaves_like '400 response' do + let(:request) { post api(route, current_user) } + end end - it 'returns 400 for missing parameters' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + context 'when branch does not exist' do + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), branch: 'foo' } + let(:message) { '404 Branch Not Found' } + end + end - expect(response).to have_http_status(400) - expect(json_response['error']).to eq('branch is missing') + context 'when commit is already included in the target branch' do + it_behaves_like '400 response' do + let(:request) { post api(route, current_user), branch: 'markdown' } + end end - it 'returns 404 if commit is not found' do - post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' + context 'when ref contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Commit Not Found') + it_behaves_like 'ref cherry-pick' end - it 'returns 404 if branch is not found' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' + context 'when ref contains a slash' do + let(:commit_id) { branch_with_slash.name } - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Branch Not Found') + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), branch: 'master' } + end end - it 'returns 400 for missing parameters' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } - expect(response).to have_http_status(400) - expect(json_response['error']).to eq('branch is missing') + it_behaves_like 'ref cherry-pick' + + context 'when ref contains a dot' do + let(:commit) { project.repository.commit(branch_with_dot.name) } + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref cherry-pick' + end end end - context 'unauthorized user' do - it 'does not cherry pick the commit' do - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' + context 'when authenticated', 'as a developer' do + let(:current_user) { guest } + + before do + project.add_developer(guest) + end + + context 'when branch is protected' do + before do + create(:protected_branch, project: project, name: 'feature') + end + + it 'returns 400 if you are not allowed to push to the target branch' do + post api(route, current_user), branch: 'feature' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('You are not allowed to push into this branch') + end end end end - describe 'Post comment to commit' do - context 'authorized user' do - it 'returns comment' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' - expect(response).to have_http_status(201) - expect(json_response['note']).to eq('My comment') - expect(json_response['path']).to be_nil - expect(json_response['line']).to be_nil - expect(json_response['line_type']).to be_nil + describe 'POST /projects/:id/repository/commits/:sha/comments' do + let(:commit) { project.repository.commit } + let(:commit_id) { commit.id } + let(:note) { 'My comment' } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/comments" } + + shared_examples_for 'ref new comment' do + context 'when ref exists' do + it 'creates the comment' do + post api(route, current_user), note: note + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/commit_note') + expect(json_response['note']).to eq('My comment') + expect(json_response['path']).to be_nil + expect(json_response['line']).to be_nil + expect(json_response['line_type']).to be_nil + end end + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { post api(route, current_user), note: 'My comment' } + end + end + end + + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like '400 response' do + let(:request) { post api(route), note: 'My comment' } + end + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { post api(route), note: 'My comment' } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as an owner' do + let(:current_user) { user } + + it_behaves_like 'ref new comment' + it 'returns the inline comment' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' + post api(route, current_user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/commit_note') expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) expect(json_response['line']).to eq(1) expect(json_response['line_type']).to eq('new') end + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), note: 'My comment' } + let(:message) { '404 Commit Not Found' } + end + end + it 'returns 400 if note is missing' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_http_status(400) + post api(route, current_user) + + expect(response).to have_gitlab_http_status(400) end - it 'returns 404 if note is attached to non existent commit' do - post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' - expect(response).to have_http_status(404) + context 'when ref contains a dot' do + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref new comment' end - end - context 'unauthorized user' do - it 'does not return the diff of the selected commit' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") - expect(response).to have_http_status(401) + context 'when ref contains a slash' do + let(:commit_id) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), note: 'My comment' } + end + end + + context 'when ref contains an escaped slash' do + let(:commit_id) { CGI.escape(branch_with_slash.name) } + + it_behaves_like 'ref new comment' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'ref new comment' + + context 'when ref contains a dot' do + let(:commit_id) { branch_with_dot.name } + + it_behaves_like 'ref new comment' + end end end end diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 32439981b60..e497ec333a2 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::DeployKeys do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id) } - let(:project2) { create(:empty_project, creator_id: user.id) } + let(:project) { create(:project, creator_id: user.id) } + let(:project2) { create(:project, creator_id: user.id) } let(:deploy_key) { create(:deploy_key, public: true) } let!(:deploy_keys_project) do @@ -182,7 +182,7 @@ describe API::DeployKeys do delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) expect(response).to have_http_status(204) - end.to change{ project.deploy_keys.count }.by(-1) + end.to change { project.deploy_keys.count }.by(-1) end it 'returns 404 Not Found with invalid ID' do @@ -193,7 +193,7 @@ describe API::DeployKeys do end describe 'POST /projects/:id/deploy_keys/:key_id/enable' do - let(:project2) { create(:empty_project) } + let(:project2) { create(:project) } context 'when the user can admin the project' do it 'enables the key' do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index aae03c84e1f..87716c6fe3a 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::Environments do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:empty_project, :private, namespace: user.namespace) } + let(:project) { create(:project, :private, namespace: user.namespace) } let!(:environment) { create(:environment, project: project) } before do @@ -13,7 +13,14 @@ describe API::Environments do describe 'GET /projects/:id/environments' do context 'as member of the project' do it 'returns project environments' do - project_data_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace) + project_data_keys = %w( + id description default_branch tag_list + ssh_url_to_repo http_url_to_repo web_url + name name_with_namespace + path path_with_namespace + star_count forks_count + created_at last_activity_at + ) get api("/projects/#{project.id}/environments", user) diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 1754ba66a96..f1a26b6ce6c 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -5,7 +5,7 @@ describe API::Events do let(:user) { create(:user) } let(:non_member) { create(:user) } let(:other_user) { create(:user, username: 'otheruser') } - let(:private_project) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) } + let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } let(:closed_issue) { create(:closed_issue, project: private_project, author: user) } let!(:closed_issue_event) { create(:event, project: private_project, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) } @@ -60,7 +60,7 @@ describe API::Events do end context 'when there are multiple events from different projects' do - let(:second_note) { create(:note_on_issue, project: create(:empty_project)) } + let(:second_note) { create(:note_on_issue, project: create(:project)) } before do second_note.project.add_user(user, :developer) @@ -106,7 +106,7 @@ describe API::Events do end it 'returns 200 status for a public project' do - public_project = create(:empty_project, :public) + public_project = create(:project, :public) get api("/projects/#{public_project.id}/events") @@ -138,5 +138,35 @@ describe API::Events do expect(response).to have_http_status(404) end end + + context 'when exists some events' do + let(:merge_request1) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') } + let(:merge_request2) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') } + + before do + create_event(merge_request1) + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + get api("/projects/#{private_project.id}/events", user), target_type: :merge_request + end.count + + create_event(merge_request2) + + expect do + get api("/projects/#{private_project.id}/events", user), target_type: :merge_request + end.not_to exceed_query_limit(control_count) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.size).to eq(2) + expect(json_response.map { |r| r['target_id'] }).to match_array([merge_request1.id, merge_request2.id]) + end + + def create_event(target) + create(:event, project: private_project, author: user, target: target) + end + end end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 9e268adf950..55c998b13b8 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -80,7 +80,7 @@ describe API::Files do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository files' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -153,7 +153,7 @@ describe API::Files do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository raw files' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb index 9b24658771f..108721c6655 100644 --- a/spec/requests/api/group_milestones_spec.rb +++ b/spec/requests/api/group_milestones_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::GroupMilestones do let(:user) { create(:user) } let(:group) { create(:group, :private) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let!(:group_member) { create(:group_member, group: group, user: user) } let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') } let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') } diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb new file mode 100644 index 00000000000..2179790d098 --- /dev/null +++ b/spec/requests/api/group_variables_spec.rb @@ -0,0 +1,221 @@ +require 'spec_helper' + +describe API::GroupVariables do + let(:group) { create(:group) } + let(:user) { create(:user) } + + describe 'GET /groups/:id/variables' do + let!(:variable) { create(:ci_group_variable, group: group) } + + context 'authorized user with proper permissions' do + before do + group.add_master(user) + end + + it 'returns group variables' do + get api("/groups/#{group.id}/variables", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a(Array) + end + end + + context 'authorized user with invalid permissions' do + it 'does not return group variables' do + get api("/groups/#{group.id}/variables", user) + + expect(response).to have_http_status(403) + end + end + + context 'unauthorized user' do + it 'does not return group variables' do + get api("/groups/#{group.id}/variables") + + expect(response).to have_http_status(401) + end + end + end + + describe 'GET /groups/:id/variables/:key' do + let!(:variable) { create(:ci_group_variable, group: group) } + + context 'authorized user with proper permissions' do + before do + group.add_master(user) + end + + it 'returns group variable details' do + get api("/groups/#{group.id}/variables/#{variable.key}", user) + + expect(response).to have_http_status(200) + expect(json_response['value']).to eq(variable.value) + expect(json_response['protected']).to eq(variable.protected?) + end + + it 'responds with 404 Not Found if requesting non-existing variable' do + get api("/groups/#{group.id}/variables/non_existing_variable", user) + + expect(response).to have_http_status(404) + end + end + + context 'authorized user with invalid permissions' do + it 'does not return group variable details' do + get api("/groups/#{group.id}/variables/#{variable.key}", user) + + expect(response).to have_http_status(403) + end + end + + context 'unauthorized user' do + it 'does not return group variable details' do + get api("/groups/#{group.id}/variables/#{variable.key}") + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /groups/:id/variables' do + context 'authorized user with proper permissions' do + let!(:variable) { create(:ci_group_variable, group: group) } + + before do + group.add_master(user) + end + + it 'creates variable' do + expect do + post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true + end.to change {group.variables.count}.by(1) + + expect(response).to have_http_status(201) + expect(json_response['key']).to eq('TEST_VARIABLE_2') + expect(json_response['value']).to eq('VALUE_2') + expect(json_response['protected']).to be_truthy + end + + it 'creates variable with optional attributes' do + expect do + post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' + end.to change {group.variables.count}.by(1) + + expect(response).to have_http_status(201) + expect(json_response['key']).to eq('TEST_VARIABLE_2') + expect(json_response['value']).to eq('VALUE_2') + expect(json_response['protected']).to be_falsey + end + + it 'does not allow to duplicate variable key' do + expect do + post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2' + end.to change {group.variables.count}.by(0) + + expect(response).to have_http_status(400) + end + end + + context 'authorized user with invalid permissions' do + it 'does not create variable' do + post api("/groups/#{group.id}/variables", user) + + expect(response).to have_http_status(403) + end + end + + context 'unauthorized user' do + it 'does not create variable' do + post api("/groups/#{group.id}/variables") + + expect(response).to have_http_status(401) + end + end + end + + describe 'PUT /groups/:id/variables/:key' do + let!(:variable) { create(:ci_group_variable, group: group) } + + context 'authorized user with proper permissions' do + before do + group.add_master(user) + end + + it 'updates variable data' do + initial_variable = group.variables.first + value_before = initial_variable.value + + put api("/groups/#{group.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true + + updated_variable = group.variables.first + + expect(response).to have_http_status(200) + expect(value_before).to eq(variable.value) + expect(updated_variable.value).to eq('VALUE_1_UP') + expect(updated_variable).to be_protected + end + + it 'responds with 404 Not Found if requesting non-existing variable' do + put api("/groups/#{group.id}/variables/non_existing_variable", user) + + expect(response).to have_http_status(404) + end + end + + context 'authorized user with invalid permissions' do + it 'does not update variable' do + put api("/groups/#{group.id}/variables/#{variable.key}", user) + + expect(response).to have_http_status(403) + end + end + + context 'unauthorized user' do + it 'does not update variable' do + put api("/groups/#{group.id}/variables/#{variable.key}") + + expect(response).to have_http_status(401) + end + end + end + + describe 'DELETE /groups/:id/variables/:key' do + let!(:variable) { create(:ci_group_variable, group: group) } + + context 'authorized user with proper permissions' do + before do + group.add_master(user) + end + + it 'deletes variable' do + expect do + delete api("/groups/#{group.id}/variables/#{variable.key}", user) + + expect(response).to have_http_status(204) + end.to change {group.variables.count}.by(-1) + end + + it 'responds with 404 Not Found if requesting non-existing variable' do + delete api("/groups/#{group.id}/variables/non_existing_variable", user) + + expect(response).to have_http_status(404) + end + end + + context 'authorized user with invalid permissions' do + it 'does not delete variable' do + delete api("/groups/#{group.id}/variables/#{variable.key}", user) + + expect(response).to have_http_status(403) + end + end + + context 'unauthorized user' do + it 'does not delete variable' do + delete api("/groups/#{group.id}/variables/#{variable.key}") + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1d7adc6ac45..eba1db15da6 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -9,9 +9,9 @@ describe API::Groups do let(:admin) { create(:admin) } let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } - let!(:project1) { create(:empty_project, namespace: group1) } - let!(:project2) { create(:empty_project, namespace: group2) } - let!(:project3) { create(:empty_project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + let!(:project1) { create(:project, namespace: group1) } + let!(:project2) { create(:project, namespace: group2) } + let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do group1.add_owner(user1) @@ -167,7 +167,7 @@ describe API::Groups do describe "GET /groups/:id" do context "when authenticated as user" do it "returns one of user1's groups" do - project = create(:empty_project, namespace: group2, path: 'Foo') + project = create(:project, namespace: group2, path: 'Foo') create(:project_group_link, project: project, group: group1) get api("/groups/#{group1.id}", user1) @@ -311,7 +311,7 @@ describe API::Groups do end it 'filters the groups projects' do - public_project = create(:empty_project, :public, path: 'test1', group: group1) + public_project = create(:project, :public, path: 'test1', group: group1) get api("/groups/#{group1.id}/projects", user1), visibility: 'public' @@ -509,7 +509,7 @@ describe API::Groups do end describe "POST /groups/:id/projects/:project_id" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:project_path) { CGI.escape(project.full_path) } before(:each) do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 7a1bd76af7a..d4006fe71a2 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -56,7 +56,7 @@ describe API::Helpers do end def doorkeeper_guard_returns(value) - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } + allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value } end def error!(message, status, header) @@ -161,7 +161,7 @@ describe API::Helpers do describe "when authenticating using a user's private token" do it "returns nil for an invalid token" do env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false } expect(current_user).to be_nil end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index ce9b9ac1eb3..8a2de23716f 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -301,7 +301,7 @@ describe API::Internal do context 'project as /namespace/project' do it do - pull(key, project_with_repo_path('/' + project.path_with_namespace)) + pull(key, project_with_repo_path('/' + project.full_path)) expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy @@ -312,7 +312,7 @@ describe API::Internal do context 'project as namespace/project' do it do - pull(key, project_with_repo_path(project.path_with_namespace)) + pull(key, project_with_repo_path(project.full_path)) expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy @@ -350,7 +350,7 @@ describe API::Internal do end context "blocked user" do - let(:personal_project) { create(:empty_project, namespace: user.namespace) } + let(:personal_project) { create(:project, namespace: user.namespace) } before do user.block diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 2c44be4e447..7d120e4a234 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,11 +1,9 @@ require 'spec_helper' -describe API::Issues do - include EmailHelpers - +describe API::Issues, :mailer do set(:user) { create(:user) } set(:project) do - create(:empty_project, :public, creator_id: user.id, namespace: user.namespace) + create(:project, :public, creator_id: user.id, namespace: user.namespace) end let(:user2) { create(:user) } @@ -296,7 +294,7 @@ describe API::Issues do describe "GET /groups/:id/issues" do let!(:group) { create(:group) } - let!(:group_project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } + let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } let!(:group_closed_issue) do create :closed_issue, author: user, @@ -518,7 +516,7 @@ describe API::Issues do end it "returns 404 on private projects for other users" do - private_project = create(:empty_project, :private) + private_project = create(:project, :private) create(:issue, project: private_project) get api("/projects/#{private_project.id}/issues", non_member) @@ -527,7 +525,7 @@ describe API::Issues do end it 'returns no issues when user has access to project but not issues' do - restricted_project = create(:empty_project, :public, :issues_private) + restricted_project = create(:project, :public, :issues_private) create(:issue, project: restricted_project) get api("/projects/#{restricted_project.id}/issues", non_member) @@ -1299,7 +1297,7 @@ describe API::Issues do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:empty_project, namespace: owner.namespace) } + let(:project) { create(:project, namespace: owner.namespace) } it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.iid}", owner) @@ -1324,8 +1322,8 @@ describe API::Issues do end describe '/projects/:id/issues/:issue_iid/move' do - let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) } + let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } it 'moves an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index f7e2f1908bb..5a4257d1009 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::Labels do let(:user) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:label1) { create(:label, title: 'label1', project: project) } let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index e095053fa03..06aca698c91 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -7,7 +7,7 @@ describe API::Members do let(:stranger) { create(:user) } let(:project) do - create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 2760c4ffde2..1e8eccb9b1c 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -36,7 +36,7 @@ describe API::MergeRequests do end context 'when authenticated' do - let!(:project2) { create(:empty_project, :public, namespace: user.namespace) } + let!(:project2) { create(:project, :public, namespace: user.namespace) } let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) } let(:user2) { create(:user) } @@ -51,7 +51,7 @@ describe API::MergeRequests do end it 'does not return unauthorized merge requests' do - private_project = create(:empty_project, :private) + private_project = create(:project, :private) merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch') get api('/merge_requests', user), scope: :all @@ -293,6 +293,26 @@ describe API::MergeRequests do expect(json_response.length).to eq(0) end + it 'returns an array of labeled merge requests that are merged for a milestone' do + bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project) + + mr1 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone) + mr2 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1) + mr3 = create(:merge_request, state: "closed", source_project: project, target_project: project, milestone: milestone1) + _mr = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1) + + create(:label_link, label: bug_label, target: mr1) + create(:label_link, label: bug_label, target: mr2) + create(:label_link, label: bug_label, target: mr3) + + get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(mr2.id) + end + context "with ordering" do before do @mr_later = mr_with_later_created_and_updated_at_time @@ -306,7 +326,7 @@ describe API::MergeRequests do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end @@ -317,7 +337,7 @@ describe API::MergeRequests do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -328,7 +348,7 @@ describe API::MergeRequests do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['updated_at'] } + response_dates = json_response.map { |merge_request| merge_request['updated_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -339,7 +359,7 @@ describe API::MergeRequests do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end end @@ -557,8 +577,8 @@ describe API::MergeRequests do context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } - let!(:unrelated_project) { create(:empty_project, namespace: create(:user).namespace, creator_id: user2.id) } + let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| fork_project.team << [user2, :reporter] @@ -887,7 +907,7 @@ describe API::MergeRequests do end it 'handles external issues' do - jira_project = create(:jira_project, :public, name: 'JIR_EXT1') + jira_project = create(:jira_project, :public, :repository, name: 'JIR_EXT1') ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) issue = create(:issue, project: jira_project) description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}" @@ -909,7 +929,7 @@ describe API::MergeRequests do end it 'returns 403 if the user has no access to the merge request' do - project = create(:empty_project, :private) + project = create(:project, :private) merge_request = create(:merge_request, :simple, source_project: project) guest = create(:user) project.team << [guest, :guest] diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 4701ad585c9..75e5062a99c 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::Notes do let(:user) { create(:user) } - let!(:project) { create(:empty_project, :public, namespace: user.namespace) } + let!(:project) { create(:project, :public, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -13,12 +13,12 @@ describe API::Notes do # For testing the cross-reference of a private issue in a public issue let(:private_user) { create(:user) } let(:private_project) do - create(:empty_project, namespace: private_user.namespace) + create(:project, namespace: private_user.namespace) .tap { |p| p.team << [private_user, :master] } end let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:empty_project, :public) } + let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let!(:cross_reference_note) do @@ -272,7 +272,7 @@ describe API::Notes do context 'when user does not have access to read the noteable' do it 'responds with 404' do - project = create(:empty_project, :private) { |p| p.add_guest(user) } + project = create(:project, :private) { |p| p.add_guest(user) } issue = create(:issue, :confidential, project: project) post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), @@ -283,7 +283,7 @@ describe API::Notes do end context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:empty_project, :private)) } + let(:private_issue) { create(:issue, project: create(:project, :private)) } ## # We are posting to project user has access to, but we use issue id diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index f619b7e6eaf..7968659a1ec 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::NotificationSettings do let(:user) { create(:user) } let!(:group) { create(:group) } - let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) } describe "GET /notification_settings" do it "returns global notification settings for the current user" do @@ -72,8 +72,8 @@ describe API::NotificationSettings do expect(response).to have_http_status(200) expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level) - expect(json_response['events']['new_note']).to eq(true) - expect(json_response['events']['new_issue']).to eq(false) + expect(json_response['events']['new_note']).to be_truthy + expect(json_response['events']['new_issue']).to be_falsey end end diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb index b34555d2815..1fc0ec528b9 100644 --- a/spec/requests/api/pipeline_schedules_spec.rb +++ b/spec/requests/api/pipeline_schedules_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::PipelineSchedules do set(:developer) { create(:user) } set(:user) { create(:user) } - set(:project) { create(:project) } + set(:project) { create(:project, :repository) } before do project.add_developer(developer) @@ -53,7 +53,7 @@ describe API::PipelineSchedules do it 'returns matched pipeline schedules' do get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target - expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target))) + expect(json_response.map { |r| r['active'] }).to all(eq(active?(target))) end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 0f9330b062d..2829c243af3 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::ProjectHooks, 'ProjectHooks' do let(:user) { create(:user) } let(:user3) { create(:user) } - let!(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:hook) do create(:project_hook, :all_events_enabled, @@ -205,7 +205,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 404 if a user attempts to delete project hooks he/she does not own" do test_user = create(:user) - other_project = create(:empty_project) + other_project = create(:project) other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index fe8fdbfd7e4..72e1574b55f 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::ProjectMilestones do let(:user) { create(:user) } - let!(:project) { create(:empty_project, namespace: user.namespace ) } + let!(:project) { create(:project, namespace: user.namespace ) } let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index f220972bae3..2b541f5719e 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe API::ProjectSnippets do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:admin) { create(:admin) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 6ed68fcff09..9baac12821f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -8,8 +8,8 @@ describe API::Projects do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :developer, user: user3, project: project) } let(:user4) { create(:user) } @@ -33,7 +33,7 @@ describe API::Projects do access_level: ProjectMember::MASTER) end let(:project4) do - create(:empty_project, + create(:project, name: 'third_project', path: 'third_project', creator_id: user4.id, @@ -61,7 +61,7 @@ describe API::Projects do if defined?(additional_project) additional_project else - create(:empty_project, :public) + create(:project, :public) end expect do @@ -70,7 +70,7 @@ describe API::Projects do end end - let!(:public_project) { create(:empty_project, :public, name: 'public_project') } + let!(:public_project) { create(:project, :public, name: 'public_project') } before do project project2 @@ -103,12 +103,12 @@ describe API::Projects do context 'when some projects are in a group' do before do - create(:empty_project, :public, group: create(:group)) + create(:project, :public, group: create(:group)) end it_behaves_like 'projects response without N + 1 queries' do let(:current_user) { user } - let(:additional_project) { create(:empty_project, :public, group: create(:group)) } + let(:additional_project) { create(:project, :public, group: create(:group)) } end end @@ -186,7 +186,14 @@ describe API::Projects do context 'and with simple=true' do it 'returns a simplified version of all the projects' do - expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace) + expected_keys = %w( + id description default_branch tag_list + ssh_url_to_repo http_url_to_repo web_url + name name_with_namespace + path path_with_namespace + star_count forks_count + created_at last_activity_at + ) get api('/projects?simple=true', user) @@ -268,7 +275,7 @@ describe API::Projects do end context 'and with starred=true' do - let(:public_project) { create(:empty_project, :public) } + let(:public_project) { create(:project, :public) } before do project_member @@ -286,11 +293,11 @@ describe API::Projects do end context 'and with all query parameters' do - let!(:project5) { create(:empty_project, :public, path: 'gitlab5', namespace: create(:namespace)) } - let!(:project6) { create(:empty_project, :public, path: 'project6', namespace: user.namespace) } - let!(:project7) { create(:empty_project, :public, path: 'gitlab7', namespace: user.namespace) } - let!(:project8) { create(:empty_project, path: 'gitlab8', namespace: user.namespace) } - let!(:project9) { create(:empty_project, :public, path: 'gitlab9') } + let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) } + let!(:project6) { create(:project, :public, path: 'project6', namespace: user.namespace) } + let!(:project7) { create(:project, :public, path: 'gitlab7', namespace: user.namespace) } + let!(:project8) { create(:project, path: 'gitlab8', namespace: user.namespace) } + let!(:project9) { create(:project, :public, path: 'gitlab9') } before do user.update_attributes(starred_projects: [project5, project7, project8, project9]) @@ -539,7 +546,7 @@ describe API::Projects do end describe 'GET /users/:user_id/projects/' do - let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } + let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } it 'returns error when user not found' do get api('/users/9999/projects/') @@ -682,13 +689,14 @@ describe API::Projects do describe 'GET /projects/:id' do context 'when unauthenticated' do it 'returns the public projects' do - public_project = create(:empty_project, :public) + public_project = create(:project, :public) get api("/projects/#{public_project.id}") expect(response).to have_http_status(200) expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) + expect(json_response['default_branch']).to eq(public_project.default_branch) expect(json_response.keys).not_to include('permissions') end end @@ -766,7 +774,7 @@ describe API::Projects do it 'handles users with dots' do dot_user = create(:user, username: 'dot.user') - project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) + project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{CGI.escape(project.full_path)}", dot_user) expect(response).to have_http_status(200) @@ -831,7 +839,7 @@ describe API::Projects do end it 'filters related URIs when their feature is not enabled' do - project = create(:empty_project, :public, + project = create(:project, :public, :merge_requests_disabled, :issues_disabled, creator_id: user.id, @@ -876,7 +884,7 @@ describe API::Projects do end context 'group project' do - let(:project2) { create(:empty_project, group: create(:group)) } + let(:project2) { create(:project, group: create(:group)) } before do project2.group.add_owner(user) @@ -916,7 +924,7 @@ describe API::Projects do context 'when unauthenticated' do it_behaves_like 'project users response' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:current_user) { nil } end end @@ -1036,11 +1044,11 @@ describe API::Projects do end describe 'fork management' do - let(:project_fork_target) { create(:empty_project) } - let(:project_fork_source) { create(:empty_project, :public) } + let(:project_fork_target) { create(:project) } + let(:project_fork_source) { create(:project, :public) } describe 'POST /projects/:id/fork/:forked_from_id' do - let(:new_project_fork_source) { create(:empty_project, :public) } + let(:new_project_fork_source) { create(:project, :public) } it "is not available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -1081,7 +1089,7 @@ describe API::Projects do end context 'when users belong to project group' do - let(:project_fork_target) { create(:empty_project, group: create(:group)) } + let(:project_fork_target) { create(:project, group: create(:group)) } before do project_fork_target.group.add_owner user diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb new file mode 100644 index 00000000000..e4f9c47fb33 --- /dev/null +++ b/spec/requests/api/protected_branches_spec.rb @@ -0,0 +1,232 @@ +require 'spec_helper' + +describe API::ProtectedBranches do + let(:user) { create(:user) } + let!(:project) { create(:project, :repository) } + let(:protected_name) { 'feature' } + let(:branch_name) { protected_name } + let!(:protected_branch) do + create(:protected_branch, project: project, name: protected_name) + end + + describe "GET /projects/:id/protected_branches" do + let(:route) { "/projects/#{project.id}/protected_branches" } + + shared_examples_for 'protected branches' do + it 'returns the protected branches' do + get api(route, user), per_page: 100 + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + + protected_branch_names = json_response.map { |x| x['name'] } + expected_branch_names = project.protected_branches.map { |x| x['name'] } + expect(protected_branch_names).to match_array(expected_branch_names) + end + end + + context 'when authenticated as a master' do + before do + project.add_master(user) + end + + it_behaves_like 'protected branches' + end + + context 'when authenticated as a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + end + + describe "GET /projects/:id/protected_branches/:branch" do + let(:route) { "/projects/#{project.id}/protected_branches/#{branch_name}" } + + shared_examples_for 'protected branch' do + it 'returns the protected branch' do + get api(route, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(::Gitlab::Access::MASTER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(::Gitlab::Access::MASTER) + end + + context 'when protected branch does not exist' do + let(:branch_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, user) } + let(:message) { '404 Not found' } + end + end + end + + context 'when authenticated as a master' do + before do + project.add_master(user) + end + + it_behaves_like 'protected branch' + + context 'when protected branch contains a wildcard' do + let(:protected_name) { 'feature*' } + + it_behaves_like 'protected branch' + end + end + + context 'when authenticated as a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + end + + describe 'POST /projects/:id/protected_branches' do + let(:branch_name) { 'new_branch' } + + context 'when authenticated as a master' do + before do + project.add_master(user) + end + + it 'protects a single branch' do + post api("/projects/#{project.id}/protected_branches", user), name: branch_name + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + end + + it 'protects a single branch and developers can push' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, push_access_level: 30 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + end + + it 'protects a single branch and developers can merge' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, merge_access_level: 30 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) + end + + it 'protects a single branch and developers can push and merge' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, push_access_level: 30, merge_access_level: 30 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) + end + + it 'protects a single branch and no one can push' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, push_access_level: 0 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + end + + it 'protects a single branch and no one can merge' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, merge_access_level: 0 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) + end + + it 'protects a single branch and no one can push or merge' do + post api("/projects/#{project.id}/protected_branches", user), + name: branch_name, push_access_level: 0, merge_access_level: 0 + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) + end + + it 'returns a 409 error if the same branch is protected twice' do + post api("/projects/#{project.id}/protected_branches", user), name: protected_name + expect(response).to have_gitlab_http_status(409) + end + + context 'when branch has a wildcard in its name' do + let(:branch_name) { 'feature/*' } + + it "protects multiple branches with a wildcard in the name" do + post api("/projects/#{project.id}/protected_branches", user), name: branch_name + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(branch_name) + expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER) + end + end + end + + context 'when authenticated as a guest' do + before do + project.add_guest(user) + end + + it "returns a 403 error if guest" do + post api("/projects/#{project.id}/protected_branches/", user), name: branch_name + + expect(response).to have_gitlab_http_status(403) + end + end + end + + describe "DELETE /projects/:id/protected_branches/unprotect/:branch" do + before do + project.add_master(user) + end + + it "unprotects a single branch" do + delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user) + + expect(response).to have_gitlab_http_status(204) + end + + it "returns 404 if branch does not exist" do + delete api("/projects/#{project.id}/protected_branches/barfoo", user) + + expect(response).to have_gitlab_http_status(404) + end + + context 'when branch has a wildcard in its name' do + let(:protected_name) { 'feature*' } + + it "unprotects a wildcard branch" do + delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user) + + expect(response).to have_gitlab_http_status(204) + end + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index ca5d98c78ef..e9ee3dd679d 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -42,7 +42,7 @@ describe API::Runner do end context 'when project token is used' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'creates runner' do post api('/runners'), token: project.runners_token @@ -182,7 +182,7 @@ describe API::Runner do end describe '/api/v4/jobs' do - let(:project) { create(:empty_project, shared_runners_enabled: false) } + let(:project) { create(:project, shared_runners_enabled: false) } let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') } let(:runner) { create(:ci_runner) } let!(:job) do @@ -683,7 +683,7 @@ describe API::Runner do end context 'when job has been updated recently' do - it { expect{ patch_the_trace }.not_to change { job.updated_at }} + it { expect { patch_the_trace }.not_to change { job.updated_at }} it "changes the job's trace" do patch_the_trace @@ -692,7 +692,7 @@ describe API::Runner do end context 'when Runner makes a force-patch' do - it { expect{ force_patch_the_trace }.not_to change { job.updated_at }} + it { expect { force_patch_the_trace }.not_to change { job.updated_at }} it "doesn't change the build.trace" do force_patch_the_trace diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 645a5389850..c8ff25f70fa 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -5,8 +5,8 @@ describe API::Runners do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id) } - let(:project2) { create(:empty_project, creator_id: user.id) } + let(:project) { create(:project, creator_id: user.id) } + let(:project2) { create(:project, creator_id: user.id) } let!(:shared_runner) { create(:ci_runner, :shared) } let!(:unused_specific_runner) { create(:ci_runner) } @@ -36,7 +36,7 @@ describe API::Runners do it 'returns user available runners' do get api('/runners', user) - shared = json_response.any?{ |r| r['is_shared'] } + shared = json_response.any? { |r| r['is_shared'] } expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -46,7 +46,7 @@ describe API::Runners do it 'filters runners by scope' do get api('/runners?scope=active', user) - shared = json_response.any?{ |r| r['is_shared'] } + shared = json_response.any? { |r| r['is_shared'] } expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -74,7 +74,7 @@ describe API::Runners do it 'returns all runners' do get api('/runners/all', admin) - shared = json_response.any?{ |r| r['is_shared'] } + shared = json_response.any? { |r| r['is_shared'] } expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -93,7 +93,7 @@ describe API::Runners do it 'filters runners by scope' do get api('/runners/all?scope=specific', admin) - shared = json_response.any?{ |r| r['is_shared'] } + shared = json_response.any? { |r| r['is_shared'] } expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -277,7 +277,7 @@ describe API::Runners do delete api("/runners/#{shared_runner.id}", admin) expect(response).to have_http_status(204) - end.to change{ Ci::Runner.shared.count }.by(-1) + end.to change { Ci::Runner.shared.count }.by(-1) end end @@ -287,7 +287,7 @@ describe API::Runners do delete api("/runners/#{unused_specific_runner.id}", admin) expect(response).to have_http_status(204) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end it 'deletes used runner' do @@ -295,7 +295,7 @@ describe API::Runners do delete api("/runners/#{specific_runner.id}", admin) expect(response).to have_http_status(204) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end end @@ -330,7 +330,7 @@ describe API::Runners do delete api("/runners/#{specific_runner.id}", user) expect(response).to have_http_status(204) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end end end @@ -349,7 +349,7 @@ describe API::Runners do it "returns project's runners" do get api("/projects/#{project.id}/runners", user) - shared = json_response.any?{ |r| r['is_shared'] } + shared = json_response.any? { |r| r['is_shared'] } expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -385,14 +385,14 @@ describe API::Runners do it 'enables specific runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id - end.to change{ project.runners.count }.by(+1) + end.to change { project.runners.count }.by(+1) expect(response).to have_http_status(201) end it 'avoids changes when enabling already enabled runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id - end.to change{ project.runners.count }.by(0) + end.to change { project.runners.count }.by(0) expect(response).to have_http_status(409) end @@ -401,7 +401,7 @@ describe API::Runners do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id - end.to change{ project.runners.count }.by(0) + end.to change { project.runners.count }.by(0) expect(response).to have_http_status(403) end @@ -416,7 +416,7 @@ describe API::Runners do it 'enables any specific runner' do expect do post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id - end.to change{ project.runners.count }.by(+1) + end.to change { project.runners.count }.by(+1) expect(response).to have_http_status(201) end end @@ -461,7 +461,7 @@ describe API::Runners do delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) expect(response).to have_http_status(204) - end.to change{ project.runners.count }.by(-1) + end.to change { project.runners.count }.by(-1) end end @@ -469,7 +469,7 @@ describe API::Runners do it "does not disable project's runner" do expect do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user) - end.to change{ project.runners.count }.by(0) + end.to change { project.runners.count }.by(0) expect(response).to have_http_status(403) end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 95df3429314..48d99841385 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -4,7 +4,7 @@ describe API::Services do let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } Service.available_services_names.each do |service| describe "PUT /projects/:id/services/#{service.dasherize}" do @@ -98,7 +98,7 @@ describe API::Services do end describe 'POST /projects/:id/services/:slug/trigger' do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } describe 'Mattermost Service' do let(:service_name) { 'mattermost_slash_commands' } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 373fab4d98a..d09b8bc42f1 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -52,10 +52,10 @@ describe API::Snippets do expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( public_snippet.id, public_snippet_other.id) - expect(json_response.map{ |snippet| snippet['web_url']} ).to include( + expect(json_response.map { |snippet| snippet['web_url']} ).to include( "http://localhost/snippets/#{public_snippet.id}", "http://localhost/snippets/#{public_snippet_other.id}") - expect(json_response.map{ |snippet| snippet['raw_url']} ).to include( + expect(json_response.map { |snippet| snippet['raw_url']} ).to include( "http://localhost/snippets/#{public_snippet.id}/raw", "http://localhost/snippets/#{public_snippet_other.id}/raw") end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index ef7d0c3ee41..9884c1ec206 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -1,66 +1,85 @@ require 'spec_helper' -require 'mime/types' describe API::Tags do - include RepoHelpers - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:project) { create(:project, :repository, creator: user) } - let!(:master) { create(:project_member, :master, user: user, project: project) } - let!(:guest) { create(:project_member, :guest, user: user2, project: project) } + let(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + let(:tag_name) { project.repository.find_tag('v1.1.0').name } - describe "GET /projects/:id/repository/tags" do - let(:tag_name) { project.repository.tag_names.sort.reverse.first } - let(:description) { 'Awesome release!' } + let(:project_id) { project.id } + let(:current_user) { nil } + + before do + project.add_master(user) + end + + describe 'GET /projects/:id/repository/tags' do + let(:route) { "/projects/#{project_id}/repository/tags" } shared_examples_for 'repository tags' do it 'returns the repository tags' do - get api("/projects/#{project.id}/repository/tags", current_user) + get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/tags') expect(response).to include_pagination_headers - expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) end - end - context 'when unauthenticated' do - it_behaves_like 'repository tags' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end end end - context 'when authenticated' do - it_behaves_like 'repository tags' do - let(:current_user) { user } + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'repository tags' + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } end end - context 'without releases' do - it "returns an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) + context 'when authenticated', 'as a master' do + let(:current_user) { user } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(tag_name) + it_behaves_like 'repository tags' + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository tags' + end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { get api(route, guest) } end end context 'with releases' do + let(:description) { 'Awesome release!' } + before do release = project.releases.find_or_initialize_by(tag: tag_name) release.update_attributes(description: description) end - it "returns an array of project tags with release info" do - get api("/projects/#{project.id}/repository/tags", user) + it 'returns an array of project tags with release info' do + get api(route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/tags') expect(response).to include_pagination_headers - expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) expect(json_response.first['message']).to eq('Version 1.1.0') expect(json_response.first['release']['description']).to eq(description) @@ -69,210 +88,342 @@ describe API::Tags do end describe 'GET /projects/:id/repository/tags/:tag_name' do - let(:tag_name) { project.repository.tag_names.sort.reverse.first } + let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}" } shared_examples_for 'repository tag' do - it 'returns the repository tag' do - get api("/projects/#{project.id}/repository/tags/#{tag_name}", current_user) - - expect(response).to have_http_status(200) + it 'returns the repository branch' do + get api(route, current_user) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/tag') expect(json_response['name']).to eq(tag_name) end - it 'returns 404 for an invalid tag name' do - get api("/projects/#{project.id}/repository/tags/foobar", current_user) + context 'when tag does not exist' do + let(:tag_name) { 'unknown' } - expect(response).to have_http_status(404) + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Tag Not Found' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end end end - context 'when unauthenticated' do - it_behaves_like 'repository tag' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'repository tag' + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } end end - context 'when authenticated' do - it_behaves_like 'repository tag' do - let(:current_user) { user } + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'repository tag' + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository tag' + end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { get api(route, guest) } end end end describe 'POST /projects/:id/repository/tags' do - context 'lightweight tags' do + let(:tag_name) { 'new_tag' } + let(:route) { "/projects/#{project_id}/repository/tags" } + + shared_examples_for 'repository new tag' do it 'creates a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master' + post api(route, current_user), tag_name: tag_name, ref: 'master' - expect(response).to have_http_status(201) - expect(json_response['name']).to eq('v7.0.1') + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/tag') + expect(json_response['name']).to eq(tag_name) end - end - context 'lightweight tags with release notes' do - it 'creates a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master', - release_description: 'Wow' + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(201) - expect(json_response['name']).to eq('v7.0.1') - expect(json_response['release']['description']).to eq('Wow') + it_behaves_like '403 response' do + let(:request) { post api(route, current_user) } + end end end - describe 'DELETE /projects/:id/repository/tags/:tag_name' do - let(:tag_name) { project.repository.tag_names.sort.reverse.first } + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { post api(route) } + let(:message) { '404 Project Not Found' } + end + end - before do - allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true) + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { post api(route, guest) } end + end + + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + context "when a protected branch doesn't already exist" do + it_behaves_like 'repository new tag' - context 'delete tag' do - it 'deletes an existing tag' do - delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user) + context 'when tag contains a dot' do + let(:tag_name) { 'v7.0.1' } - expect(response).to have_http_status(204) + it_behaves_like 'repository new tag' end - it 'raises 404 if the tag does not exist' do - delete api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response).to have_http_status(404) + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository new tag' + + context 'when tag contains a dot' do + let(:tag_name) { 'v7.0.1' } + + it_behaves_like 'repository new tag' + end end end - end - context 'annotated tag' do - it 'creates a new annotated tag' do - # Identity must be set in .gitconfig to create annotated tag. - repo_path = project.repository.path_to_repo - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + it 'returns 400 if tag name is invalid' do + post api(route, current_user), tag_name: 'new design', ref: 'master' + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'returns 400 if tag already exists' do + post api(route, current_user), tag_name: 'new_design1', ref: 'master' - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.1.0', - ref: 'master', - message: 'Release 7.1.0' + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/tag') - expect(response).to have_http_status(201) - expect(json_response['name']).to eq('v7.1.0') - expect(json_response['message']).to eq('Release 7.1.0') + post api(route, current_user), tag_name: 'new_design1', ref: 'master' + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Tag new_design1 already exists') end - end - it 'denies for user without push access' do - post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.9.0', - ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response).to have_http_status(403) + it 'returns 400 if ref name is invalid' do + post api(route, current_user), tag_name: 'new_design3', ref: 'foo' + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Target foo is invalid') + end + + context 'lightweight tags with release notes' do + it 'creates a new tag' do + post api(route, current_user), tag_name: tag_name, ref: 'master', release_description: 'Wow' + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/tag') + expect(json_response['name']).to eq(tag_name) + expect(json_response['release']['description']).to eq('Wow') + end + end + + context 'annotated tag' do + it 'creates a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + + post api(route, current_user), tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0' + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/tag') + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end + end end + end + + describe 'DELETE /projects/:id/repository/tags/:tag_name' do + let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}" } - it 'returns 400 if tag name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v 1.0.0', - ref: 'master' - expect(response).to have_http_status(400) - expect(json_response['message']).to eq('Tag name invalid') + before do + allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true) end - it 'returns 400 if tag already exists' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response).to have_http_status(201) - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response).to have_http_status(400) - expect(json_response['message']).to eq('Tag v8.0.0 already exists') + shared_examples_for 'repository delete tag' do + it 'deletes a tag' do + delete api(route, current_user) + + expect(response).to have_gitlab_http_status(204) + end + + context 'when tag does not exist' do + let(:tag_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { delete api(route, current_user) } + let(:message) { 'No such tag' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { delete api(route, current_user) } + end + end end - it 'returns 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'mytag', - ref: 'foo' - expect(response).to have_http_status(400) - expect(json_response['message']).to eq('Target foo is invalid') + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'repository delete tag' + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository delete tag' + end end end describe 'POST /projects/:id/repository/tags/:tag_name/release' do - let(:tag_name) { project.repository.tag_names.first } + let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } let(:description) { 'Awesome release!' } - it 'creates description for existing git tag' do - post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), - description: description + shared_examples_for 'repository new release' do + it 'creates description for existing git tag' do + post api(route, user), description: description - expect(response).to have_http_status(201) - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(description) - end + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/release') + expect(json_response['tag_name']).to eq(tag_name) + expect(json_response['description']).to eq(description) + end + + context 'when tag does not exist' do + let(:tag_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), description: description } + let(:message) { 'Tag does not exist' } + end + end - it 'returns 404 if the tag does not exist' do - post api("/projects/#{project.id}/repository/tags/foobar/release", user), - description: description + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('Tag does not exist') + it_behaves_like '403 response' do + let(:request) { post api(route, current_user), description: description } + end + end end - context 'on tag with existing release' do - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update_attributes(description: description) + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'repository new release' + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository new release' end - it 'returns 409 if there is already a release' do - post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), - description: description + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'returns 409 if there is already a release' do + post api(route, user), description: description - expect(response).to have_http_status(409) - expect(json_response['message']).to eq('Release already exists') + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']).to eq('Release already exists') + end end end end describe 'PUT id/repository/tags/:tag_name/release' do - let(:tag_name) { project.repository.tag_names.first } + let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } let(:description) { 'Awesome release!' } let(:new_description) { 'The best release!' } - context 'on tag with existing release' do - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update_attributes(description: description) + shared_examples_for 'repository update release' do + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'updates the release description' do + put api(route, current_user), description: new_description + + expect(response).to have_gitlab_http_status(200) + expect(json_response['tag_name']).to eq(tag_name) + expect(json_response['description']).to eq(new_description) + end end - it 'updates the release description' do - put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), - description: new_description + context 'when tag does not exist' do + let(:tag_name) { 'unknown' } - expect(response).to have_http_status(200) - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(new_description) + it_behaves_like '404 response' do + let(:request) { put api(route, current_user), description: new_description } + let(:message) { 'Tag does not exist' } + end end - end - it 'returns 404 if the tag does not exist' do - put api("/projects/#{project.id}/repository/tags/foobar/release", user), - description: new_description + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('Tag does not exist') + it_behaves_like '403 response' do + let(:request) { put api(route, current_user), description: new_description } + end + end end - it 'returns 404 if the release does not exist' do - put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), - description: new_description + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'repository update release' - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('Release does not exist') + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository update release' + end + + context 'when release does not exist' do + it_behaves_like '404 response' do + let(:request) { put api(route, current_user), description: new_description } + let(:message) { 'Release does not exist' } + end + end end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 9fc73c6e092..25d7f6dffcf 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe API::Todos do - let(:project_1) { create(:project) } - let(:project_2) { create(:empty_project) } + let(:project_1) { create(:project, :repository) } + let(:project_2) { create(:project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 153596c2975..572e9a0fd07 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -13,7 +13,7 @@ describe API::Triggers do let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') } describe 'POST /projects/:project_id/trigger/pipeline' do - let!(:project2) { create(:project) } + let!(:project2) { create(:project, :repository) } let(:options) do { token: trigger_token @@ -185,7 +185,7 @@ describe API::Triggers do expect do post api("/projects/#{project.id}/triggers", user), description: 'trigger' - end.to change{project.triggers.count}.by(1) + end.to change {project.triggers.count}.by(1) expect(response).to have_http_status(201) expect(json_response).to include('description' => 'trigger') @@ -288,7 +288,7 @@ describe API::Triggers do delete api("/projects/#{project.id}/triggers/#{trigger.id}", user) expect(response).to have_http_status(204) - end.to change{project.triggers.count}.by(-1) + end.to change {project.triggers.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing trigger' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 66b165b438b..2dc7be22f8f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -16,38 +16,44 @@ describe API::Users do it "returns authorization error when the `username` parameter is not passed" do get api("/users") - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns the user when a valid `username` parameter is passed" do - user = create(:user) - get api("/users"), username: user.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response[0]['id']).to eq(user.id) expect(json_response[0]['username']).to eq(user.username) end - it "returns authorization error when the `username` parameter refers to an inaccessible user" do - user = create(:user) - - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - - get api("/users"), username: user.username - - expect(response).to have_http_status(403) - end - it "returns an empty response when an invalid `username` parameter is passed" do get api("/users"), username: 'invalid' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(0) end + + context "when public level is restricted" do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it "returns authorization error when the `username` parameter refers to an inaccessible user" do + get api("/users"), username: user.username + + expect(response).to have_gitlab_http_status(403) + end + + it "returns authorization error when the `username` parameter is not passed" do + get api("/users") + + expect(response).to have_gitlab_http_status(403) + end + end end context "when authenticated" do @@ -58,10 +64,10 @@ describe API::Users do end context 'when authenticate as a regular user' do - it "renders 403" do + it "renders 200" do get api("/users", user) - expect(response).to have_gitlab_http_status(403) + expect(response).to have_gitlab_http_status(200) end end diff --git a/spec/requests/api/v3/award_emoji_spec.rb b/spec/requests/api/v3/award_emoji_spec.rb index 9234710f488..681e8e04295 100644 --- a/spec/requests/api/v3/award_emoji_spec.rb +++ b/spec/requests/api/v3/award_emoji_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::V3::AwardEmoji do let(:user) { create(:user) } - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb index 4d786331d1b..b86aab2ec70 100644 --- a/spec/requests/api/v3/boards_spec.rb +++ b/spec/requests/api/v3/boards_spec.rb @@ -4,7 +4,7 @@ describe API::V3::Boards do let(:user) { create(:user) } let(:guest) { create(:user) } let(:non_member) { create(:user) } - let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:dev_label) do create(:label, title: 'Development', color: '#FFAABB', project: project) @@ -99,7 +99,7 @@ describe API::V3::Boards do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:empty_project, namespace: owner.namespace) } + let(:project) { create(:project, namespace: owner.namespace) } it "deletes the list if an admin requests it" do delete v3_api("#{base_url}/#{dev_list.id}", owner) diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb index 94f4d93a8dc..2affd0cfa51 100644 --- a/spec/requests/api/v3/deploy_keys_spec.rb +++ b/spec/requests/api/v3/deploy_keys_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::V3::DeployKeys do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id) } - let(:project2) { create(:empty_project, creator_id: user.id) } + let(:project) { create(:project, creator_id: user.id) } + let(:project2) { create(:project, creator_id: user.id) } let(:deploy_key) { create(:deploy_key, public: true) } let!(:deploy_keys_project) do @@ -87,7 +87,7 @@ describe API::V3::DeployKeys do expect do post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs - end.to change{ project.deploy_keys.count }.by(1) + end.to change { project.deploy_keys.count }.by(1) end it 'returns an existing ssh key when attempting to add a duplicate' do @@ -122,7 +122,7 @@ describe API::V3::DeployKeys do it 'should delete existing key' do expect do delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin) - end.to change{ project.deploy_keys.count }.by(-1) + end.to change { project.deploy_keys.count }.by(-1) end it 'should return 404 Not Found with invalid ID' do @@ -133,7 +133,7 @@ describe API::V3::DeployKeys do end describe "POST /projects/:id/#{path}/:key_id/enable" do - let(:project2) { create(:empty_project) } + let(:project2) { create(:project) } context 'when the user can admin the project' do it 'enables the key' do diff --git a/spec/requests/api/v3/environments_spec.rb b/spec/requests/api/v3/environments_spec.rb index 99f35723974..39264e819a3 100644 --- a/spec/requests/api/v3/environments_spec.rb +++ b/spec/requests/api/v3/environments_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::V3::Environments do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:empty_project, :private, namespace: user.namespace) } + let(:project) { create(:project, :private, namespace: user.namespace) } let!(:environment) { create(:environment, project: project) } before do diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb index 8b2d165c763..4ffa5d1784e 100644 --- a/spec/requests/api/v3/files_spec.rb +++ b/spec/requests/api/v3/files_spec.rb @@ -74,7 +74,7 @@ describe API::V3::Files do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository files' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 5cdc528e190..10756e494c3 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -9,9 +9,9 @@ describe API::V3::Groups do let(:admin) { create(:admin) } let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } - let!(:project1) { create(:empty_project, namespace: group1) } - let!(:project2) { create(:empty_project, namespace: group2) } - let!(:project3) { create(:empty_project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + let!(:project1) { create(:project, namespace: group1) } + let!(:project2) { create(:project, namespace: group2) } + let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do group1.add_owner(user1) @@ -165,7 +165,7 @@ describe API::V3::Groups do describe "GET /groups/:id" do context "when authenticated as user" do it "returns one of user1's groups" do - project = create(:empty_project, namespace: group2, path: 'Foo') + project = create(:project, namespace: group2, path: 'Foo') create(:project_group_link, project: project, group: group1) get v3_api("/groups/#{group1.id}", user1) @@ -307,7 +307,7 @@ describe API::V3::Groups do end it 'filters the groups projects' do - public_project = create(:empty_project, :public, path: 'test1', group: group1) + public_project = create(:project, :public, path: 'test1', group: group1) get v3_api("/groups/#{group1.id}/projects", user1), visibility: 'public' @@ -501,7 +501,7 @@ describe API::V3::Groups do end describe "POST /groups/:id/projects/:project_id" do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:project_path) { CGI.escape(project.full_path) } before(:each) do diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb index 4dff09b6df8..9eb538c4b09 100644 --- a/spec/requests/api/v3/issues_spec.rb +++ b/spec/requests/api/v3/issues_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe API::V3::Issues do - include EmailHelpers - +describe API::V3::Issues, :mailer do let(:user) { create(:user) } let(:user2) { create(:user) } let(:non_member) { create(:user) } @@ -10,7 +8,7 @@ describe API::V3::Issues do let(:author) { create(:author) } let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -243,7 +241,7 @@ describe API::V3::Issues do describe "GET /groups/:id/issues" do let!(:group) { create(:group) } - let!(:group_project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } + let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } let!(:group_closed_issue) do create :closed_issue, author: user, @@ -453,7 +451,7 @@ describe API::V3::Issues do end it "returns 404 on private projects for other users" do - private_project = create(:empty_project, :private) + private_project = create(:project, :private) create(:issue, project: private_project) get v3_api("/projects/#{private_project.id}/issues", non_member) @@ -462,7 +460,7 @@ describe API::V3::Issues do end it 'returns no issues when user has access to project but not issues' do - restricted_project = create(:empty_project, :public, issues_access_level: ProjectFeature::PRIVATE) + restricted_project = create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) create(:issue, project: restricted_project) get v3_api("/projects/#{restricted_project.id}/issues", non_member) @@ -1172,7 +1170,7 @@ describe API::V3::Issues do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:empty_project, namespace: owner.namespace) } + let(:project) { create(:project, namespace: owner.namespace) } it "deletes the issue if an admin requests it" do delete v3_api("/projects/#{project.id}/issues/#{issue.id}", owner) @@ -1192,8 +1190,8 @@ describe API::V3::Issues do end describe '/projects/:id/issues/:issue_id/move' do - let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) } + let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } it 'moves an issue' do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb index 62faa1cb129..32f37a08024 100644 --- a/spec/requests/api/v3/labels_spec.rb +++ b/spec/requests/api/v3/labels_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::V3::Labels do let(:user) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:label1) { create(:label, title: 'label1', project: project) } let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb index 623f02902b8..bc918a8eb02 100644 --- a/spec/requests/api/v3/members_spec.rb +++ b/spec/requests/api/v3/members_spec.rb @@ -7,7 +7,7 @@ describe API::V3::Members do let(:stranger) { create(:user) } let(:project) do - create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb index 4f9e63f2ace..18d0a804137 100644 --- a/spec/requests/api/v3/merge_requests_spec.rb +++ b/spec/requests/api/v3/merge_requests_spec.rb @@ -90,7 +90,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end @@ -99,7 +99,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -108,7 +108,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['updated_at'] } + response_dates = json_response.map { |merge_request| merge_request['updated_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -117,7 +117,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) - response_dates = json_response.map{ |merge_request| merge_request['created_at'] } + response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end end @@ -312,8 +312,8 @@ describe API::MergeRequests do context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } - let!(:unrelated_project) { create(:empty_project, namespace: create(:user).namespace, creator_id: user2.id) } + let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| fork_project.team << [user2, :reporter] @@ -635,7 +635,7 @@ describe API::MergeRequests do end it 'handles external issues' do - jira_project = create(:jira_project, :public, name: 'JIR_EXT1') + jira_project = create(:jira_project, :public, :repository, name: 'JIR_EXT1') issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project) merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}") @@ -650,7 +650,7 @@ describe API::MergeRequests do end it 'returns 403 if the user has no access to the merge request' do - project = create(:empty_project, :private) + project = create(:project, :private, :repository) merge_request = create(:merge_request, :simple, source_project: project) guest = create(:user) project.team << [guest, :guest] diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb index f04efc990a7..feaa87faec7 100644 --- a/spec/requests/api/v3/milestones_spec.rb +++ b/spec/requests/api/v3/milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::V3::Milestones do let(:user) { create(:user) } - let!(:project) { create(:empty_project, namespace: user.namespace ) } + let!(:project) { create(:project, namespace: user.namespace ) } let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:milestone) { create(:milestone, project: project) } @@ -194,7 +194,7 @@ describe API::V3::Milestones do end describe 'confidential issues' do - let(:public_project) { create(:empty_project, :public) } + let(:public_project) { create(:project, :public) } let(:milestone) { create(:milestone, project: public_project) } let(:issue) { create(:issue, project: public_project) } let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb index b5f98a9a545..56729692eed 100644 --- a/spec/requests/api/v3/notes_spec.rb +++ b/spec/requests/api/v3/notes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe API::V3::Notes do let(:user) { create(:user) } - let!(:project) { create(:empty_project, :public, namespace: user.namespace) } + let!(:project) { create(:project, :public, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -13,12 +13,12 @@ describe API::V3::Notes do # For testing the cross-reference of a private issue in a public issue let(:private_user) { create(:user) } let(:private_project) do - create(:empty_project, namespace: private_user.namespace) + create(:project, namespace: private_user.namespace) .tap { |p| p.team << [private_user, :master] } end let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:empty_project, :public) } + let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let!(:cross_reference_note) do @@ -268,7 +268,7 @@ describe API::V3::Notes do context 'when user does not have access to read the noteable' do it 'responds with 404' do - project = create(:empty_project, :private) { |p| p.add_guest(user) } + project = create(:project, :private) { |p| p.add_guest(user) } issue = create(:issue, :confidential, project: project) post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), @@ -279,7 +279,7 @@ describe API::V3::Notes do end context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:empty_project, :private)) } + let(:private_issue) { create(:issue, project: create(:project, :private)) } ## # We are posting to project user has access to, but we use issue id diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb index 1950c64c690..3963924a066 100644 --- a/spec/requests/api/v3/project_snippets_spec.rb +++ b/spec/requests/api/v3/project_snippets_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe API::ProjectSnippets do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:admin) { create(:admin) } @@ -30,7 +30,7 @@ describe API::ProjectSnippets do expect(response).to have_http_status(200) expect(json_response.size).to eq(3) - expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + expect(json_response.map { |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) expect(json_response.last).to have_key('web_url') end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index bbfcaab1ea1..fca5b5b5d82 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -7,8 +7,8 @@ describe API::V3::Projects do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :developer, user: user3, project: project) } let(:user4) { create(:user) } @@ -31,7 +31,7 @@ describe API::V3::Projects do access_level: ProjectMember::MASTER) end let(:project4) do - create(:empty_project, + create(:project, name: 'third_project', path: 'third_project', creator_id: user4.id, @@ -82,7 +82,14 @@ describe API::V3::Projects do context 'GET /projects?simple=true' do it 'returns a simplified version of all the projects' do - expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace) + expected_keys = %w( + id description default_branch tag_list + ssh_url_to_repo http_url_to_repo web_url + name name_with_namespace + path path_with_namespace + star_count forks_count + created_at last_activity_at + ) get v3_api('/projects?simple=true', user) @@ -125,7 +132,7 @@ describe API::V3::Projects do end context 'and using archived' do - let!(:archived_project) { create(:empty_project, creator_id: user.id, namespace: user.namespace, archived: true) } + let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) } it 'returns archived project' do get v3_api('/projects?archived=true', user) @@ -281,7 +288,7 @@ describe API::V3::Projects do end end - let!(:public_project) { create(:empty_project, :public) } + let!(:public_project) { create(:project, :public) } before do project project2 @@ -312,7 +319,7 @@ describe API::V3::Projects do end describe 'GET /projects/starred' do - let(:public_project) { create(:empty_project, :public) } + let(:public_project) { create(:project, :public) } before do project_member @@ -637,13 +644,14 @@ describe API::V3::Projects do describe 'GET /projects/:id' do context 'when unauthenticated' do it 'returns the public projects' do - public_project = create(:empty_project, :public) + public_project = create(:project, :public) get v3_api("/projects/#{public_project.id}") expect(response).to have_http_status(200) expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) + expect(json_response['default_branch']).to eq(public_project.default_branch) expect(json_response.keys).not_to include('permissions') end end @@ -718,7 +726,7 @@ describe API::V3::Projects do it 'handles users with dots' do dot_user = create(:user, username: 'dot.user') - project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) + project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user) expect(response).to have_http_status(200) @@ -766,7 +774,7 @@ describe API::V3::Projects do end context 'group project' do - let(:project2) { create(:empty_project, group: create(:group)) } + let(:project2) { create(:project, group: create(:group)) } before { project2.group.add_owner(user) } @@ -811,7 +819,7 @@ describe API::V3::Projects do context 'when unauthenticated' do it_behaves_like 'project events response' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:current_user) { nil } end end @@ -861,7 +869,7 @@ describe API::V3::Projects do context 'when unauthenticated' do it_behaves_like 'project users response' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:current_user) { nil } end end @@ -975,11 +983,11 @@ describe API::V3::Projects do end describe 'fork management' do - let(:project_fork_target) { create(:empty_project) } - let(:project_fork_source) { create(:empty_project, :public) } + let(:project_fork_target) { create(:project) } + let(:project_fork_source) { create(:project, :public) } describe 'POST /projects/:id/fork/:forked_from_id' do - let(:new_project_fork_source) { create(:empty_project, :public) } + let(:new_project_fork_source) { create(:project, :public) } it "is not available for non admin users" do post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -1020,7 +1028,7 @@ describe API::V3::Projects do end context 'when users belong to project group' do - let(:project_fork_target) { create(:empty_project, group: create(:group)) } + let(:project_fork_target) { create(:project, group: create(:group)) } before do project_fork_target.group.add_owner user @@ -1140,16 +1148,16 @@ describe API::V3::Projects do describe 'GET /projects/search/:query' do let!(:query) { 'query'} - let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } - let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } - let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:pre_post) { create(:empty_project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:unfound) { create(:empty_project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } - let!(:internal) { create(:empty_project, :internal, name: "internal #{query}") } - let!(:unfound_internal) { create(:empty_project, :internal, name: 'unfound internal') } - let!(:public) { create(:empty_project, :public, name: "public #{query}") } - let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } - let!(:one_dot_two) { create(:empty_project, :public, name: "one.dot.two") } + let!(:search) { create(:project, name: query, creator_id: user.id, namespace: user.namespace) } + let!(:pre) { create(:project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } + let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } + let!(:internal) { create(:project, :internal, name: "internal #{query}") } + let!(:unfound_internal) { create(:project, :internal, name: 'unfound internal') } + let!(:public) { create(:project, :public, name: "public #{query}") } + let!(:unfound_public) { create(:project, :public, name: 'unfound public') } + let!(:one_dot_two) { create(:project, :public, name: "one.dot.two") } shared_examples_for 'project search response' do |args = {}| it 'returns project search responses' do diff --git a/spec/requests/api/v3/runners_spec.rb b/spec/requests/api/v3/runners_spec.rb index dbda2cf34c3..a31eb3f1d43 100644 --- a/spec/requests/api/v3/runners_spec.rb +++ b/spec/requests/api/v3/runners_spec.rb @@ -5,8 +5,8 @@ describe API::V3::Runners do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id) } - let(:project2) { create(:empty_project, creator_id: user.id) } + let(:project) { create(:project, creator_id: user.id) } + let(:project2) { create(:project, creator_id: user.id) } let!(:shared_runner) { create(:ci_runner, :shared) } let!(:unused_specific_runner) { create(:ci_runner) } @@ -38,7 +38,7 @@ describe API::V3::Runners do delete v3_api("/runners/#{shared_runner.id}", admin) expect(response).to have_http_status(200) - end.to change{ Ci::Runner.shared.count }.by(-1) + end.to change { Ci::Runner.shared.count }.by(-1) end end @@ -48,7 +48,7 @@ describe API::V3::Runners do delete v3_api("/runners/#{unused_specific_runner.id}", admin) expect(response).to have_http_status(200) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end it 'deletes used runner' do @@ -56,7 +56,7 @@ describe API::V3::Runners do delete v3_api("/runners/#{specific_runner.id}", admin) expect(response).to have_http_status(200) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end end @@ -91,7 +91,7 @@ describe API::V3::Runners do delete v3_api("/runners/#{specific_runner.id}", user) expect(response).to have_http_status(200) - end.to change{ Ci::Runner.specific.count }.by(-1) + end.to change { Ci::Runner.specific.count }.by(-1) end end end @@ -113,7 +113,7 @@ describe API::V3::Runners do delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) expect(response).to have_http_status(200) - end.to change{ project.runners.count }.by(-1) + end.to change { project.runners.count }.by(-1) end end @@ -121,7 +121,7 @@ describe API::V3::Runners do it "does not disable project's runner" do expect do delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user) - end.to change{ project.runners.count }.by(0) + end.to change { project.runners.count }.by(0) expect(response).to have_http_status(403) end end diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb index 3ba62de822a..f0fa48e22df 100644 --- a/spec/requests/api/v3/services_spec.rb +++ b/spec/requests/api/v3/services_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe API::V3::Services do let(:user) { create(:user) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } available_services = Service.available_services_names available_services.delete('prometheus') diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb index 1bc2258ebd3..9ead3cad8bb 100644 --- a/spec/requests/api/v3/snippets_spec.rb +++ b/spec/requests/api/v3/snippets_spec.rb @@ -45,10 +45,10 @@ describe API::V3::Snippets do expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( public_snippet.id, public_snippet_other.id) - expect(json_response.map{ |snippet| snippet['web_url']} ).to include( + expect(json_response.map { |snippet| snippet['web_url']} ).to include( "http://localhost/snippets/#{public_snippet.id}", "http://localhost/snippets/#{public_snippet_other.id}") - expect(json_response.map{ |snippet| snippet['raw_url']} ).to include( + expect(json_response.map { |snippet| snippet['raw_url']} ).to include( "http://localhost/snippets/#{public_snippet.id}/raw", "http://localhost/snippets/#{public_snippet_other.id}/raw") end diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb index 9c2c4d64257..8f5c3fbf8dd 100644 --- a/spec/requests/api/v3/todos_spec.rb +++ b/spec/requests/api/v3/todos_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe API::V3::Todos do - let(:project_1) { create(:empty_project) } - let(:project_2) { create(:empty_project) } + let(:project_1) { create(:project) } + let(:project_2) { create(:project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb index 60212660fb6..075de2c0cba 100644 --- a/spec/requests/api/v3/triggers_spec.rb +++ b/spec/requests/api/v3/triggers_spec.rb @@ -171,7 +171,7 @@ describe API::V3::Triggers do it 'creates trigger' do expect do post v3_api("/projects/#{project.id}/triggers", user) - end.to change{project.triggers.count}.by(1) + end.to change {project.triggers.count}.by(1) expect(response).to have_http_status(201) expect(json_response).to be_a(Hash) @@ -202,7 +202,7 @@ describe API::V3::Triggers do delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user) expect(response).to have_http_status(200) - end.to change{project.triggers.count}.by(-1) + end.to change {project.triggers.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing trigger' do diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb index de7499a4e43..bc0a4ab20a3 100644 --- a/spec/requests/api/v3/users_spec.rb +++ b/spec/requests/api/v3/users_spec.rb @@ -232,7 +232,7 @@ describe API::V3::Users do describe 'GET /users/:id/events' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } before do @@ -276,7 +276,7 @@ describe API::V3::Users do end context 'when there are multiple events from different projects' do - let(:second_note) { create(:note_on_issue, project: create(:empty_project)) } + let(:second_note) { create(:note_on_issue, project: create(:project)) } let(:third_note) { create(:note_on_issue, project: project) } before do diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index e0975024b80..48592e12822 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::Variables do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:empty_project, creator_id: user.id) } + let!(:project) { create(:project, creator_id: user.id) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:variable) { create(:ci_variable, project: project) } @@ -74,7 +74,7 @@ describe API::Variables do it 'creates variable' do expect do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true - end.to change{project.variables.count}.by(1) + end.to change {project.variables.count}.by(1) expect(response).to have_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') @@ -85,7 +85,7 @@ describe API::Variables do it 'creates variable with optional attributes' do expect do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' - end.to change{project.variables.count}.by(1) + end.to change {project.variables.count}.by(1) expect(response).to have_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') @@ -96,7 +96,7 @@ describe API::Variables do it 'does not allow to duplicate variable key' do expect do post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' - end.to change{project.variables.count}.by(0) + end.to change {project.variables.count}.by(0) expect(response).to have_http_status(400) end @@ -166,7 +166,7 @@ describe API::Variables do delete api("/projects/#{project.id}/variables/#{variable.key}", user) expect(response).to have_http_status(204) - end.to change{project.variables.count}.by(-1) + end.to change {project.variables.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing variable' do diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 49e815ee16c..ebd67eb1e94 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::API::Builds do let(:runner) { FactoryGirl.create(:ci_runner, tag_list: %w(mysql ruby)) } - let(:project) { FactoryGirl.create(:empty_project, shared_runners_enabled: false) } + let(:project) { FactoryGirl.create(:project, shared_runners_enabled: false) } let(:last_update) { nil } describe "Builds API for runners" do @@ -421,7 +421,7 @@ describe Ci::API::Builds do end context 'when build has been updated recently' do - it { expect{ patch_the_trace }.not_to change { build.updated_at }} + it { expect { patch_the_trace }.not_to change { build.updated_at }} it 'changes the build trace' do patch_the_trace @@ -430,7 +430,7 @@ describe Ci::API::Builds do end context 'when Runner makes a force-patch' do - it { expect{ force_patch_the_trace }.not_to change { build.updated_at }} + it { expect { force_patch_the_trace }.not_to change { build.updated_at }} it "doesn't change the build.trace" do force_patch_the_trace diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index 78b2be350cd..75059dd20a0 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -70,7 +70,7 @@ describe Ci::API::Runners do end context 'when project token is provided' do - let(:project) { FactoryGirl.create(:empty_project) } + let(:project) { FactoryGirl.create(:project) } before do post ci_api("/runners/register"), token: project.runners_token diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index e481ca916ab..7c77ebb69a2 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -4,7 +4,7 @@ describe Ci::API::Triggers do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } let!(:project) { create(:project, :repository, ci_id: 10) } - let!(:project2) { create(:empty_project, ci_id: 11) } + let!(:project2) { create(:project, ci_id: 11) } let!(:trigger) do create(:ci_trigger, diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index d4a3e8b13e1..ecac40e301b 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -123,7 +123,7 @@ describe 'Git HTTP requests' do context "when requesting the Wiki" do let(:wiki) { ProjectWiki.new(project) } - let(:path) { "/#{wiki.repository.path_with_namespace}.git" } + let(:path) { "/#{wiki.repository.full_path}.git" } context "when the project is public" do let(:project) { create(:project, :repository, :public, :wiki_enabled) } @@ -139,7 +139,7 @@ describe 'Git HTTP requests' do download(path) do |response| json_body = ActiveSupport::JSON.decode(response.body) - expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + expect(json_body['RepoPath']).to include(wiki.repository.full_path) end end end @@ -222,7 +222,7 @@ describe 'Git HTTP requests' do end context "when the project exists" do - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } context "when the project is public" do let(:project) { create(:project, :repository, :public) } @@ -286,7 +286,7 @@ describe 'Git HTTP requests' do context 'when the request is not from gitlab-workhorse' do it 'raises an exception' do expect do - get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack") + get("/#{project.full_path}.git/info/refs?service=git-upload-pack") end.to raise_error(JWT::DecodeError) end end @@ -294,7 +294,7 @@ describe 'Git HTTP requests' do context 'when the repo is public' do context 'but the repo is disabled' do let(:project) { create(:project, :public, :repository, :repository_disabled) } - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } let(:env) { {} } it_behaves_like 'pulls require Basic HTTP Authentication' @@ -303,7 +303,7 @@ describe 'Git HTTP requests' do context 'but the repo is enabled' do let(:project) { create(:project, :public, :repository, :repository_enabled) } - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } let(:env) { {} } it_behaves_like 'pulls are allowed' @@ -421,7 +421,7 @@ describe 'Git HTTP requests' do @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") end - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } let(:env) { { user: 'oauth2', password: @token.token } } it_behaves_like 'pulls are allowed' @@ -431,7 +431,7 @@ describe 'Git HTTP requests' do context 'when user has 2FA enabled' do let(:user) { create(:user, :two_factor) } let(:access_token) { create(:personal_access_token, user: user) } - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } before do project.team << [user, :master] @@ -573,14 +573,14 @@ describe 'Git HTTP requests' do context "when a gitlab ci token is provided" do let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, :running) } - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } before do build.update!(project: project) # can't associate it on factory create end context 'when build created by system is authenticated' do - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } let(:env) { { user: 'gitlab-ci-token', password: build.token } } it_behaves_like 'pulls are allowed' @@ -602,7 +602,7 @@ describe 'Git HTTP requests' do # We are "authenticated" as CI using a valid token here. But we are # not authorized to see any other project, so return "not found". it "rejects pulls for other project with 404 Not Found" do - clone_get("#{other_project.path_with_namespace}.git", env) + clone_get("#{other_project.full_path}.git", env) expect(response).to have_http_status(:not_found) expect(response.body).to eq(git_access_error(:project_not_found)) @@ -616,13 +616,13 @@ describe 'Git HTTP requests' do end shared_examples 'can download code only' do - let(:path) { "#{project.path_with_namespace}.git" } + let(:path) { "#{project.full_path}.git" } let(:env) { { user: 'gitlab-ci-token', password: build.token } } it_behaves_like 'pulls are allowed' context 'when the repo does not exist' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'rejects pulls with 403 Forbidden' do clone_get path, env @@ -646,7 +646,7 @@ describe 'Git HTTP requests' do it_behaves_like 'can download code only' it 'downloads from other project get status 403' do - clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token expect(response).to have_http_status(:forbidden) end @@ -658,7 +658,7 @@ describe 'Git HTTP requests' do it_behaves_like 'can download code only' it 'downloads from other project get status 404' do - clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token expect(response).to have_http_status(:not_found) end @@ -671,7 +671,7 @@ describe 'Git HTTP requests' do let(:project) { create(:project, :repository, :public, path: 'project.git-project') } context "GET info/refs" do - let(:path) { "/#{project.path_with_namespace}/info/refs" } + let(:path) { "/#{project.full_path}/info/refs" } context "when no params are added" do before do @@ -679,7 +679,7 @@ describe 'Git HTTP requests' do end it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs") + expect(response).to redirect_to("/#{project.full_path}.git/info/refs") end end @@ -691,7 +691,7 @@ describe 'Git HTTP requests' do end it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") end end @@ -703,7 +703,7 @@ describe 'Git HTTP requests' do end it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") end end @@ -722,13 +722,13 @@ describe 'Git HTTP requests' do context "POST git-upload-pack" do it "fails to find a route" do - expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + expect { clone_post(project.full_path) }.to raise_error(ActionController::RoutingError) end end context "POST git-receive-pack" do it "fails to find a route" do - expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + expect { push_post(project.full_path) }.to raise_error(ActionController::RoutingError) end end end @@ -744,7 +744,7 @@ describe 'Git HTTP requests' do Blob.decorate(Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt'), project) end - get "/#{project.path_with_namespace}/blob/master/info/refs" + get "/#{project.full_path}/blob/master/info/refs" end it "returns the file" do @@ -754,7 +754,7 @@ describe 'Git HTTP requests' do context "when the file does not exist" do before do - get "/#{project.path_with_namespace}/blob/master/info/refs" + get "/#{project.full_path}/blob/master/info/refs" end it "returns not found" do diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 697b150ab34..27d09b8202e 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -20,7 +20,7 @@ describe 'Git LFS API and storage' do let(:sample_size) { lfs_object.size } describe 'when lfs is disabled' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:body) do { 'objects' => [ @@ -46,7 +46,7 @@ describe 'Git LFS API and storage' do end context 'project specific LFS settings' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:body) do { 'objects' => [ @@ -151,7 +151,7 @@ describe 'Git LFS API and storage' do end describe 'deprecated API' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do enable_lfs @@ -188,7 +188,7 @@ describe 'Git LFS API and storage' do end describe 'when fetching lfs object' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:update_permissions) { } before do @@ -281,7 +281,7 @@ describe 'Git LFS API and storage' do shared_examples 'can download LFS only from own projects' do context 'for owned project' do - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } let(:update_permissions) do project.lfs_objects << lfs_object @@ -302,7 +302,7 @@ describe 'Git LFS API and storage' do end context 'for other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:update_permissions) do @@ -368,7 +368,7 @@ describe 'Git LFS API and storage' do end describe 'download' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:body) do { 'operation' => 'download', @@ -408,7 +408,7 @@ describe 'Git LFS API and storage' do end context 'when downloading an lfs object that is assigned to other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:update_lfs_permissions) do other_project.lfs_objects << lfs_object end @@ -559,7 +559,7 @@ describe 'Git LFS API and storage' do end context 'for other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } it 'rejects downloading code' do @@ -662,7 +662,7 @@ describe 'Git LFS API and storage' do end context 'when pushing an lfs object that already exists' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:update_lfs_permissions) do other_project.lfs_objects << lfs_object end @@ -701,7 +701,7 @@ describe 'Git LFS API and storage' do expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first['size']).to eq(1575078) - expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.full_path}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization) end end @@ -765,7 +765,7 @@ describe 'Git LFS API and storage' do end context 'tries to push to other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } @@ -806,7 +806,7 @@ describe 'Git LFS API and storage' do end describe 'unsupported' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:authorization) { authorize_user } let(:body) do { @@ -894,7 +894,7 @@ describe 'Git LFS API and storage' do end describe 'to one project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe 'when user is authenticated' do let(:authorization) { authorize_user } @@ -986,7 +986,7 @@ describe 'Git LFS API and storage' do end context 'tries to push to other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } @@ -1086,7 +1086,7 @@ describe 'Git LFS API and storage' do end context 'tries to push to other project' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } @@ -1111,7 +1111,7 @@ describe 'Git LFS API and storage' do end describe 'and second project not related to fork or a source project' do - let(:second_project) { create(:empty_project) } + let(:second_project) { create(:project) } let(:authorization) { authorize_user } before do diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb index 51fbfecec4b..9afeb2983b0 100644 --- a/spec/requests/request_profiler_spec.rb +++ b/spec/requests/request_profiler_spec.rb @@ -15,7 +15,7 @@ describe 'Request Profiler' do it 'creates a profile of the request' do project = create(:project, namespace: user.namespace) time = Time.now - path = "/#{project.path_with_namespace}" + path = "/#{project.full_path}" Timecop.freeze(time) do get path, nil, 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb index 9b70c2a24be..aacbe300966 100644 --- a/spec/routing/environments_spec.rb +++ b/spec/routing/environments_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'environments routing' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:environment) do create(:environment, project: project, diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index c02409b2e0b..39d44245c3f 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -165,15 +165,19 @@ describe 'project routing' do # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit describe Projects::RepositoriesController, 'routing' do it 'to #archive' do - expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master') end it 'to #archive format:zip' do - expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip') + expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master') end it 'to #archive format:tar.bz2' do - expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2') + expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master') + end + + it 'to #archive with "/" in route' do + expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome') end end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index 86e703a6448..9f26d5cd09a 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -47,7 +47,7 @@ describe AnalyticsBuildEntity do let(:finished_at) { nil } it 'does not blow up' do - expect{ subject[:date] }.not_to raise_error + expect { subject[:date] }.not_to raise_error end it 'shows the right message' do @@ -63,7 +63,7 @@ describe AnalyticsBuildEntity do let(:started_at) { nil } it 'does not blow up' do - expect{ subject[:date] }.not_to raise_error + expect { subject[:date] }.not_to raise_error end it 'shows the right message' do @@ -79,7 +79,7 @@ describe AnalyticsBuildEntity do let(:finished_at) { nil } it 'does not blow up' do - expect{ subject[:date] }.not_to raise_error + expect { subject[:date] }.not_to raise_error end it 'shows the right message' do diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb index 75d606d5eb3..89588b4df2b 100644 --- a/spec/serializers/analytics_issue_entity_spec.rb +++ b/spec/serializers/analytics_issue_entity_spec.rb @@ -13,7 +13,7 @@ describe AnalyticsIssueEntity do } end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:request) { EntityRequest.new(project: project, entity: :merge_request) } let(:entity) do diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index 7c14c198a74..5befc28f4fa 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsIssueSerializer do end let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:resource) do { total_time: "172802.724419", diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb index 56cb08acfc6..62067cc0ef2 100644 --- a/spec/serializers/analytics_merge_request_serializer_spec.rb +++ b/spec/serializers/analytics_merge_request_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsMergeRequestSerializer do end let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:resource) do { total_time: "172802.724419", diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb index 5d7a94c2d02..236c244b402 100644 --- a/spec/serializers/analytics_summary_serializer_spec.rb +++ b/spec/serializers/analytics_summary_serializer_spec.rb @@ -5,7 +5,7 @@ describe AnalyticsSummarySerializer do described_class.new.represent(resource) end - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:resource) do diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 1332572fffc..5b7822d5d8e 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -57,7 +57,7 @@ describe BuildDetailsEntity do context 'when merge request is from a fork' do let(:fork_project) do - create(:empty_project, forked_from_project: project) + create(:project, forked_from_project: project) end let(:pipeline) { create(:ci_pipeline, project: fork_project) } diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb index 8149de869f1..d3aefa2c9eb 100644 --- a/spec/serializers/deploy_key_entity_spec.rb +++ b/spec/serializers/deploy_key_entity_spec.rb @@ -4,9 +4,9 @@ describe DeployKeyEntity do include RequestAwareEntity let(:user) { create(:user) } - let(:project) { create(:empty_project, :internal)} - let(:project_private) { create(:empty_project, :private)} - let!(:project_pending_delete) { create(:empty_project, :internal, pending_delete: true) } + let(:project) { create(:project, :internal)} + let(:project_private) { create(:project, :private)} + let!(:project_pending_delete) { create(:project, :internal, pending_delete: true) } let(:deploy_key) { create(:deploy_key) } let!(:deploy_key_internal) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) } let!(:deploy_key_private) { create(:deploy_keys_project, project: project_private, deploy_key: deploy_key) } diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 4c52a00b442..ca9b520fb38 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe EnvironmentSerializer do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:json) do described_class @@ -39,7 +39,7 @@ describe EnvironmentSerializer do end context 'when there is a collection of objects provided' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:resource) { create_list(:environment, 2) } it 'contains important elements of environment' do diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index b3d58b2636f..a2fd5b7daae 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe MergeRequestEntity do - let(:project) { create :empty_project } + let(:project) { create :project } let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } @@ -47,7 +47,7 @@ describe MergeRequestEntity do :cancel_merge_when_pipeline_succeeds_path, :create_issue_to_resolve_discussions_path, :source_branch_path, :target_branch_commits_path, - :target_branch_tree_path, :commits_count) + :target_branch_tree_path, :commits_count, :merge_ongoing) end it 'has email_patches_path' do diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb index b990370a271..f60d1843581 100644 --- a/spec/serializers/pipeline_details_entity_spec.rb +++ b/spec/serializers/pipeline_details_entity_spec.rb @@ -42,7 +42,7 @@ describe PipelineDetailsEntity do end context 'when pipeline is retryable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_pipeline, status: :success, project: project) @@ -70,7 +70,7 @@ describe PipelineDetailsEntity do end context 'when pipeline is cancelable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_pipeline, status: :running, project: project) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 5b01cc4fc9e..881f2b6bfd8 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -42,7 +42,7 @@ describe PipelineEntity do end context 'when pipeline is retryable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_pipeline, status: :success, project: project) @@ -70,7 +70,7 @@ describe PipelineEntity do end context 'when pipeline is cancelable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_pipeline, status: :running, project: project) diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 262bc4acb69..2de8daba6b5 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -100,7 +100,7 @@ describe PipelineSerializer do context 'number of queries' do let(:resource) { Ci::Pipeline.all } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do Ci::Pipeline::AVAILABLE_STATUSES.each do |status| @@ -111,7 +111,7 @@ describe PipelineSerializer do shared_examples 'no N+1 queries' do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(1).of(59) + expect(recorded.count).to be_within(1).of(57) expect(recorded.cached_count).to eq(0) end end diff --git a/spec/serializers/runner_entity_spec.rb b/spec/serializers/runner_entity_spec.rb index 4f25a8dcfa0..439ba2cbca2 100644 --- a/spec/serializers/runner_entity_spec.rb +++ b/spec/serializers/runner_entity_spec.rb @@ -4,7 +4,7 @@ describe RunnerEntity do let(:runner) { create(:ci_runner, :specific) } let(:entity) { described_class.new(runner, request: request, current_user: user) } let(:request) { double('request') } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:admin) } before do diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 66a8a93b168..1c2d0b3e0dc 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -8,7 +8,7 @@ describe Auth::ContainerRegistryAuthenticationService do let(:payload) { JWT.decode(subject[:token], rsa_key).first } let(:authentication_abilities) do - [:read_container_image, :create_container_image] + [:read_container_image, :create_container_image, :admin_container_image] end subject do @@ -46,7 +46,7 @@ describe Auth::ContainerRegistryAuthenticationService do shared_examples 'an accessible' do let(:access) do [{ 'type' => 'repository', - 'name' => project.path_with_namespace, + 'name' => project.full_path, 'actions' => actions }] end @@ -59,6 +59,12 @@ describe Auth::ContainerRegistryAuthenticationService do it { expect(payload).to include('access' => []) } end + shared_examples 'a deletable' do + it_behaves_like 'an accessible' do + let(:actions) { ['*'] } + end + end + shared_examples 'a pullable' do it_behaves_like 'an accessible' do let(:actions) { ['pull'] } @@ -96,8 +102,8 @@ describe Auth::ContainerRegistryAuthenticationService do end describe '#full_access_token' do - let(:project) { create(:empty_project) } - let(:token) { described_class.full_access_token(project.path_with_namespace) } + let(:project) { create(:project) } + let(:token) { described_class.full_access_token(project.full_path) } subject { { token: token } } @@ -112,7 +118,7 @@ describe Auth::ContainerRegistryAuthenticationService do let(:current_user) { create(:user) } context 'for private project' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } context 'allow to use scope-less authentication' do it_behaves_like 'a valid token' @@ -120,25 +126,38 @@ describe Auth::ContainerRegistryAuthenticationService do context 'allow developer to push images' do before do - project.team << [current_user, :developer] + project.add_developer(current_user) end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + { scope: "repository:#{project.full_path}:push" } end it_behaves_like 'a pushable' it_behaves_like 'container repository factory' end + context 'disallow developer to delete images' do + before do + project.add_developer(current_user) + end + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:*" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + context 'allow reporter to pull images' do before do - project.team << [current_user, :reporter] + project.add_reporter(current_user) end context 'when pulling from root level repository' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end it_behaves_like 'a pullable' @@ -146,13 +165,26 @@ describe Auth::ContainerRegistryAuthenticationService do end end + context 'disallow reporter to delete images' do + before do + project.add_reporter(current_user) + end + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:*" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + context 'return a least of privileges' do before do - project.team << [current_user, :reporter] + project.add_reporter(current_user) end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push,pull" } + { scope: "repository:#{project.full_path}:push,pull" } end it_behaves_like 'a pullable' @@ -161,11 +193,24 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow guest to pull or push images' do before do - project.team << [current_user, :guest] + project.add_guest(current_user) + end + + let(:current_params) do + { scope: "repository:#{project.full_path}:pull,push" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + + context 'disallow guest to delete images' do + before do + project.add_guest(current_user) end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull,push" } + { scope: "repository:#{project.path_with_namespace}:*" } end it_behaves_like 'an inaccessible' @@ -174,11 +219,11 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'allow anyone to pull images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end it_behaves_like 'a pullable' @@ -187,7 +232,16 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to push images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + { scope: "repository:#{project.full_path}:push" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + + context 'disallow anyone to delete images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:*" } end it_behaves_like 'an inaccessible' @@ -205,12 +259,12 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for internal project' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } context 'for internal user' do context 'allow anyone to pull images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end it_behaves_like 'a pullable' @@ -219,7 +273,16 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to push images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + { scope: "repository:#{project.full_path}:push" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + + context 'disallow anyone to delete images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:*" } end it_behaves_like 'an inaccessible' @@ -228,19 +291,56 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for external user' do - let(:current_user) { create(:user, external: true) } - let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull,push" } + context 'disallow anyone to pull or push images' do + let(:current_user) { create(:user, external: true) } + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull,push" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' end - it_behaves_like 'an inaccessible' - it_behaves_like 'not a container repository factory' + context 'disallow anyone to delete images' do + let(:current_user) { create(:user, external: true) } + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:*" } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + end + end + end + + context 'delete authorized as master' do + let(:current_project) { create(:project) } + let(:current_user) { create(:user) } + + let(:authentication_abilities) do + [:admin_container_image] + end + + before do + current_project.add_master(current_user) + end + + it_behaves_like 'a valid token' + + context 'allow to delete images' do + let(:current_params) do + { scope: "repository:#{current_project.path_with_namespace}:*" } + end + + it_behaves_like 'a deletable' do + let(:project) { current_project } end end end context 'build authorized as user' do - let(:current_project) { create(:empty_project) } + let(:current_project) { create(:project) } let(:current_user) { create(:user) } let(:authentication_abilities) do @@ -248,14 +348,14 @@ describe Auth::ContainerRegistryAuthenticationService do end before do - current_project.team << [current_user, :developer] + current_project.add_developer(current_user) end it_behaves_like 'a valid token' context 'allow to pull and push images' do let(:current_params) do - { scope: "repository:#{current_project.path_with_namespace}:pull,push" } + { scope: "repository:#{current_project.full_path}:pull,push" } end it_behaves_like 'a pullable and pushable' do @@ -267,14 +367,24 @@ describe Auth::ContainerRegistryAuthenticationService do end end + context 'disallow to delete images' do + let(:current_params) do + { scope: "repository:#{current_project.path_with_namespace}:*" } + end + + it_behaves_like 'an inaccessible' do + let(:project) { current_project } + end + end + context 'for other projects' do context 'when pulling' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end context 'allow for public' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' @@ -288,7 +398,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'when you are member' do before do - project.team << [current_user, :developer] + project.add_developer(current_user) end it_behaves_like 'a pullable' @@ -296,7 +406,7 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'when you are owner' do - let(:project) { create(:empty_project, namespace: current_user.namespace) } + let(:project) { create(:project, namespace: current_user.namespace) } it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' @@ -304,7 +414,7 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for private' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } it_behaves_like 'pullable for being team member' @@ -318,7 +428,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'when you are member' do before do - project.team << [current_user, :developer] + project.add_developer(current_user) end it_behaves_like 'a pullable' @@ -326,7 +436,7 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'when you are owner' do - let(:project) { create(:empty_project, namespace: current_user.namespace) } + let(:project) { create(:project, namespace: current_user.namespace) } it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' @@ -337,15 +447,15 @@ describe Auth::ContainerRegistryAuthenticationService do context 'when pushing' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + { scope: "repository:#{project.full_path}:push" } end context 'disallow for all' do context 'when you are member' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } before do - project.team << [current_user, :developer] + project.add_developer(current_user) end it_behaves_like 'an inaccessible' @@ -353,7 +463,7 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'when you are owner' do - let(:project) { create(:empty_project, :public, namespace: current_user.namespace) } + let(:project) { create(:project, :public, namespace: current_user.namespace) } it_behaves_like 'an inaccessible' it_behaves_like 'not a container repository factory' @@ -363,7 +473,7 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for project without container registry' do - let(:project) { create(:empty_project, :public, container_registry_enabled: false) } + let(:project) { create(:project, :public, container_registry_enabled: false) } before do project.update(container_registry_enabled: false) @@ -371,7 +481,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow when pulling' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end it_behaves_like 'an inaccessible' @@ -396,21 +506,21 @@ describe Auth::ContainerRegistryAuthenticationService do end context 'for private project' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + { scope: "repository:#{project.full_path}:pull" } end it_behaves_like 'a forbidden' end context 'for public project' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } context 'when pulling and pushing' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull,push" } + { scope: "repository:#{project.full_path}:pull,push" } end it_behaves_like 'a pullable' @@ -419,7 +529,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'when pushing' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + { scope: "repository:#{project.full_path}:push" } end it_behaves_like 'a forbidden' diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index 6e3227303fe..db51a524e79 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::CreateService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject(:service) { described_class.new(project, double) } diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index 23ad66e454b..f2ddaa903da 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::Issues::CreateService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index b1b5d807a78..01ee3856c99 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Boards::Issues::ListService do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 15a32350ae2..63dfe80d672 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Boards::Issues::MoveService do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board1) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb index a95b12eeaa3..1d0be99fb35 100644 --- a/spec/services/boards/list_service_spec.rb +++ b/spec/services/boards/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::ListService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject(:service) { described_class.new(project, double) } diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index 86d3227a78d..7d0b396cd06 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::Lists::CreateService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index ad6ae83286d..bd98625b44f 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::Lists::DestroyService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 7dec9f7a4a5..592f25059ac 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::Lists::GenerateService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index c93788d4516..b189857e4f4 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Boards::Lists::ListService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:label) { create(:label, project: project) } let!(:list) { create(:list, board: board, label: label) } diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 43e125a21df..a9d218bad49 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Boards::Lists::MoveService do describe '#execute' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 4ec495f612e..730df4e0336 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -75,7 +75,7 @@ describe Ci::CreatePipelineService do end context 'when merge request target project is different from source project' do - let!(:target_project) { create(:project) } + let!(:target_project) { create(:project, :repository) } let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) } it 'updates head pipeline for merge request' do diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index 945a2fe1a6b..9a6875e448c 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::PipelineTriggerService, services: true do +describe Ci::PipelineTriggerService do let(:project) { create(:project, :repository) } before do @@ -18,7 +18,7 @@ describe Ci::PipelineTriggerService, services: true do context 'when trigger belongs to a different project' do let(:params) { { token: trigger.token, ref: 'master', variables: nil } } - let(:trigger) { create(:ci_trigger, project: create(:empty_project), owner: user) } + let(:trigger) { create(:ci_trigger, project: create(:project), owner: user) } it 'does nothing' do expect { result }.not_to change { Ci::Pipeline.count } diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index 1ced26ff98d..330ec81e87d 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::PlayBuildService, '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, :manual, pipeline: pipeline) } @@ -11,7 +11,7 @@ describe Ci::PlayBuildService, '#execute' do end context 'when project does not have repository yet' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'allows user to play build if protected branch rules are met' do project.add_developer(user) @@ -33,7 +33,7 @@ describe Ci::PlayBuildService, '#execute' do end context 'when project has repository' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'allows user with developer role to play a build' do project.add_developer(user) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 3a702742681..214adc9960f 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::ProcessPipelineService, '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) do create(:ci_empty_pipeline, ref: 'master', project: project) diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 23c0f715c3e..8eb0d2d10a4 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Ci describe RegisterJobService do - let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false } + let!(:project) { FactoryGirl.create :project, shared_runners_enabled: false } let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline } let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) } @@ -72,9 +72,9 @@ module Ci end context 'for multiple builds' do - let!(:project2) { create :empty_project, shared_runners_enabled: true } + let!(:project2) { create :project, shared_runners_enabled: true } let!(:pipeline2) { create :ci_pipeline, project: project2 } - let!(:project3) { create :empty_project, shared_runners_enabled: true } + let!(:project3) { create :project, shared_runners_enabled: true } let!(:pipeline3) { create :ci_pipeline, project: project3 } let!(:build1_project1) { pending_build } let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index fd8a57d28f0..cec667071cc 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::RetryBuildService do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 90b0c5a4dce..6ce75c65c8c 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::RetryPipelineService, '#execute' do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb index 4b872d667cf..03c682ae0d7 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/delete_merged_branches_service_spec.rb @@ -32,6 +32,14 @@ describe DeleteMergedBranchesService do expect(project.repository.branch_names).to include('improve/awesome') end + it 'keeps wildcard protected branches' do + create(:protected_branch, project: project, name: 'improve/*') + + service.execute + + expect(project.repository.branch_names).to include('improve/awesome') + end + context 'user without rights' do let(:user) { create(:user) } diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 00104ae1fd9..42adb044190 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -114,7 +114,7 @@ describe EventCreateService do end describe '#push', :clean_gitlab_redis_shared_state do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } it 'creates a new event' do @@ -128,7 +128,7 @@ describe EventCreateService do describe 'Project' do let(:user) { create :user } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe '#join_project' do subject { service.join_project(project, user) } diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 3fb677b65be..a6449a3c9f5 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -132,7 +132,7 @@ describe GitPushService, services: true do end it "creates a new pipeline" do - expect{ subject }.to change{ Ci::Pipeline.count } + expect { subject }.to change { Ci::Pipeline.count } expect(Ci::Pipeline.last).to be_push end end @@ -453,7 +453,7 @@ describe GitPushService, services: true do let(:message) { "this is some work.\n\ncloses JIRA-1" } let(:comment_body) do { - body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.path_with_namespace}/commit/#{closing_commit.id}]." + body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]." }.to_json end diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index f877c145390..05695aa8188 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -39,7 +39,7 @@ describe GitTagPushService do end it "creates a new pipeline" do - expect{ subject }.to change{ Ci::Pipeline.count } + expect { subject }.to change { Ci::Pipeline.count } expect(Ci::Pipeline.last).to be_push end end diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index c18870ea100..1b2ce3cd03e 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -6,7 +6,7 @@ describe Groups::DestroyService do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } - let!(:project) { create(:empty_project, namespace: group) } + let!(:project) { create(:project, namespace: group) } let!(:notification_setting) { create(:notification_setting, source: group)} let!(:gitlab_shell) { Gitlab::Shell.new } let!(:remove_path) { group.path + "+#{group.id}+deleted" } diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 9902e1aff8b..44f22a3b37b 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -13,7 +13,7 @@ describe Groups::UpdateService do before do public_group.add_user(user, Gitlab::Access::MASTER) - create(:empty_project, :public, group: public_group) + create(:project, :public, group: public_group) end it "does not change permission level" do @@ -27,7 +27,7 @@ describe Groups::UpdateService do before do internal_group.add_user(user, Gitlab::Access::MASTER) - create(:empty_project, :internal, group: internal_group) + create(:project, :internal, group: internal_group) end it "does not change permission level" do @@ -69,7 +69,7 @@ describe Groups::UpdateService do before do internal_group.add_user(user, Gitlab::Access::MASTER) - create(:empty_project, :internal, group: internal_group) + create(:project, :internal, group: internal_group) end it 'returns true' do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 055aa6156cb..bdaab88d673 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Issuable::BulkUpdateService do let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace) } def bulk_update(issuables, extra_params = {}) bulk_update_params = extra_params @@ -118,7 +118,7 @@ describe Issuable::BulkUpdateService do context 'when the new assignee ID is not present' do it 'does not unassign' do expect { bulk_update(issue, assignee_ids: []) } - .not_to change{ issue.reload.assignees } + .not_to change { issue.reload.assignees } end end end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index a48748e274d..9b2d9e79f4f 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issues::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '#execute' do @@ -351,14 +351,14 @@ describe Issues::CreateService do end it 'marks related spam_log as recaptcha_verified' do - expect { issue }.to change{SpamLog.last.recaptcha_verified}.from(false).to(true) + expect { issue }.to change {SpamLog.last.recaptcha_verified}.from(false).to(true) end context 'when spam log does not belong to a user' do let(:log_user) { create(:user) } it 'does not mark spam_log as recaptcha_verified' do - expect { issue }.not_to change{SpamLog.last.recaptcha_verified} + expect { issue }.not_to change {SpamLog.last.recaptcha_verified} end end end @@ -378,7 +378,7 @@ describe Issues::CreateService do end it 'creates a new spam_log' do - expect{issue}.to change{SpamLog.count}.from(0).to(1) + expect {issue}.to change {SpamLog.count}.from(0).to(1) end it 'assigns a spam_log to an issue' do diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb index ed55664e382..089e77cc88b 100644 --- a/spec/services/issues/duplicate_service_spec.rb +++ b/spec/services/issues/duplicate_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Issues::DuplicateService do let(:user) { create(:user) } - let(:canonical_project) { create(:empty_project) } - let(:duplicate_project) { create(:empty_project) } + let(:canonical_project) { create(:project) } + let(:duplicate_project) { create(:project) } let(:canonical_issue) { create(:issue, project: canonical_project) } let(:duplicate_issue) { create(:issue, project: duplicate_project) } diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 171fc7334c4..f2b35a8fadf 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -5,8 +5,8 @@ describe Issues::MoveService do let(:author) { create(:user) } let(:title) { 'Some issue' } let(:description) { 'Some issue description' } - let(:old_project) { create(:empty_project) } - let(:new_project) { create(:empty_project) } + let(:old_project) { create(:project) } + let(:new_project) { create(:project) } let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } let(:old_issue) do diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 1ff988e9b47..205e9ebd237 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issues::ReopenService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, :closed, project: project) } describe '#execute' do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index eec2096fa34..34fb16edc84 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1,13 +1,11 @@ # coding: utf-8 require 'spec_helper' -describe Issues::UpdateService do - include EmailHelpers - +describe Issues::UpdateService, :mailer do let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:label) { create(:label, project: project) } let(:label2) { create(:label) } @@ -480,7 +478,7 @@ describe Issues::UpdateService do feature_visibility_attr = :"#{issue.model_name.plural}_access_level" project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE) - expect{ update_issue(assignee_ids: [assignee.id]) }.not_to change{ issue.assignees } + expect { update_issue(assignee_ids: [assignee.id]) }.not_to change { issue.assignees } end end end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb index ecb88653001..438e6dbc628 100644 --- a/spec/services/labels/create_service_spec.rb +++ b/spec/services/labels/create_service_spec.rb @@ -4,7 +4,7 @@ describe Labels::CreateService do describe '#execute' do let(:project) { create(:project) } let(:group) { create(:group) } - + let(:hex_color) { '#FF0000' } let(:named_color) { 'red' } let(:upcase_color) { 'RED' } diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb index 2d3a15a1c11..a781fbc7f7d 100644 --- a/spec/services/labels/find_or_create_service_spec.rb +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Labels::FindOrCreateService do describe '#execute' do let(:group) { create(:group) } - let(:project) { create(:empty_project, namespace: group) } + let(:project) { create(:project, namespace: group) } let(:params) do { diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb index 7cea877ad88..8809b282127 100644 --- a/spec/services/labels/promote_service_spec.rb +++ b/spec/services/labels/promote_service_spec.rb @@ -5,7 +5,7 @@ describe Labels::PromoteService do let!(:user) { create(:user) } context 'project without group' do - let!(:project_1) { create(:empty_project) } + let!(:project_1) { create(:project) } let!(:project_label_1_1) { create(:label, project: project_1) } @@ -27,10 +27,10 @@ describe Labels::PromoteService do let!(:group_1) { create(:group) } let!(:group_2) { create(:group) } - let!(:project_1) { create(:empty_project, namespace: group_1) } - let!(:project_2) { create(:empty_project, namespace: group_1) } - let!(:project_3) { create(:empty_project, namespace: group_1) } - let!(:project_4) { create(:empty_project, namespace: group_2) } + let!(:project_1) { create(:project, namespace: group_1) } + let!(:project_2) { create(:project, namespace: group_1) } + let!(:project_3) { create(:project, namespace: group_1) } + let!(:project_4) { create(:project, namespace: group_2) } # Labels/issues can't be lazily created so we might as well eager initialize # all other objects too since we use them inside diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index f70edd3d16e..ae819c011de 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -6,8 +6,8 @@ describe Labels::TransferService do let(:group_1) { create(:group) } let(:group_2) { create(:group) } let(:group_3) { create(:group) } - let(:project_1) { create(:empty_project, namespace: group_2) } - let(:project_2) { create(:empty_project, namespace: group_3) } + let(:project_1) { create(:project, namespace: group_2) } + let(:project_2) { create(:project, namespace: group_3) } let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb index ddba96b8d03..302c488d6c6 100644 --- a/spec/services/members/approve_access_request_service_spec.rb +++ b/spec/services/members/approve_access_request_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Members::ApproveAccessRequestService do let(:user) { create(:user) } let(:access_requester) { create(:user) } - let(:project) { create(:empty_project, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable) } let(:group) { create(:group, :public, :access_requestable) } let(:opts) { {} } diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb index f0abbc46ca3..2d04d824180 100644 --- a/spec/services/members/authorized_destroy_service_spec.rb +++ b/spec/services/members/authorized_destroy_service_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Members::AuthorizedDestroyService do let(:member_user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:group) { create(:group, :public) } - let(:group_project) { create(:empty_project, :public, group: group) } + let(:group_project) { create(:project, :public, group: group) } def number_of_assigned_issuables(user) Issue.assigned_to(user).count + MergeRequest.assigned_to(user).count diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index c73a229823d..2a793e0eb4d 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Members::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:project_user) { create(:user) } diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 0e30b343eaf..72f5e27180d 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Members::DestroyService do let(:user) { create(:user) } let(:member_user) { create(:user) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:group) { create(:group, :public) } shared_examples 'a service raising ActiveRecord::RecordNotFound' do diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb index f39d4f47904..0a704bba521 100644 --- a/spec/services/members/request_access_service_spec.rb +++ b/spec/services/members/request_access_service_spec.rb @@ -29,7 +29,7 @@ describe Members::RequestAccessService do end context 'when current user cannot request access to the project' do - %i[empty_project group].each do |source_type| + %i[project group].each do |source_type| it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do let(:source) { create(source_type, :private) } end @@ -37,7 +37,7 @@ describe Members::RequestAccessService do end context 'when access requests are disabled' do - %i[empty_project group].each do |source_type| + %i[project group].each do |source_type| it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do let(:source) { create(source_type, :public) } end @@ -45,7 +45,7 @@ describe Members::RequestAccessService do end context 'when current user can request access to the project' do - %i[empty_project group].each do |source_type| + %i[project group].each do |source_type| it_behaves_like 'a service creating a access request' do let(:source) { create(source_type, :public, :access_requestable) } end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index ea192e51f89..b46c419de14 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -264,8 +264,8 @@ describe MergeRequests::BuildService do end context 'upstream project has disabled merge requests' do - let(:upstream_project) { create(:empty_project, :merge_requests_disabled) } - let(:project) { create(:empty_project, forked_from_project: upstream_project) } + let(:upstream_project) { create(:project, :merge_requests_disabled) } + let(:project) { create(:project, forked_from_project: upstream_project) } let(:commits) { Commit.decorate([commit_1], project) } it 'sets target project correctly' do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index dd3ac9c4ac6..681feee61d1 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe MergeRequests::UpdateService do - include EmailHelpers - +describe MergeRequests::UpdateService, :mailer do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } @@ -511,7 +509,7 @@ describe MergeRequests::UpdateService do feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level" project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE) - expect{ update_merge_request(assignee_id: assignee) }.not_to change{ merge_request.assignee } + expect { update_merge_request(assignee_id: assignee) }.not_to change { merge_request.assignee } end end end diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index fa0686d8061..2bdf224804d 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Milestones::CloseService do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } before do diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index c6fe8e65912..8837b91051d 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Milestones::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '#execute' do diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb index dda579c7080..a6cc2251e48 100644 --- a/spec/services/note_summary_spec.rb +++ b/spec/services/note_summary_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe NoteSummary do - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:noteable) { build(:issue) } let(:user) { build(:user) } diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index c08a5a940bb..661d26946e7 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Notes::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } let(:opts) do diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb index 4330190caaa..c9a99a43edb 100644 --- a/spec/services/notes/destroy_service_spec.rb +++ b/spec/services/notes/destroy_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Notes::DestroyService do describe '#execute' do it 'deletes a note' do - project = create(:empty_project) + project = create(:project) issue = create(:issue, project: project) note = create(:note, project: project, noteable: issue) diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index bf9320e5fce..a2b3638059f 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Notes::PostProcessService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index fc4cd3dc2b7..0280a19098b 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Notes::QuickActionsService do shared_context 'note on noteable' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } @@ -225,7 +225,7 @@ describe Notes::QuickActionsService do context 'CE restriction for issue assignees' do describe '/assign' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } let(:master) { create(:user) } diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index dea2dc02d00..3210539f3ee 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Notes::UpdateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb deleted file mode 100644 index 77233dc1b2f..00000000000 --- a/spec/services/notification_recipient_service_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe NotificationRecipientService do - set(:user) { create(:user) } - set(:project) { create(:empty_project, :public) } - set(:issue) { create(:issue, project: project) } - - set(:watcher) do - watcher = create(:user) - setting = watcher.notification_settings_for(project) - setting.level = :watch - setting.save - - watcher - end - - subject { described_class.new(project) } - - describe '#build_recipients' do - it 'does not modify the participants of the target' do - expect { subject.build_recipients(issue, user, action: :new_issue) } - .not_to change { issue.participants(user) } - end - end - - describe '#build_new_note_recipients' do - set(:note) { create(:note_on_issue, noteable: issue, project: project) } - - it 'does not modify the participants of the target' do - expect { subject.build_new_note_recipients(note) } - .not_to change { note.noteable.participants(note.author) } - end - end -end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 5b69426cbaa..bd8ff5aaaa7 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe NotificationService do - include EmailHelpers - +describe NotificationService, :mailer do let(:notification) { described_class.new } let(:assignee) { create(:user) } @@ -14,7 +12,6 @@ describe NotificationService do shared_examples 'notifications for new mentions' do def send_notifications(*new_mentions) - reset_delivered_emails! notification.send(notification_method, mentionable, new_mentions, @u_disabled) end @@ -88,7 +85,7 @@ describe NotificationService do it { expect(notification.new_key(key)).to be_truthy } it 'sends email to key owner' do - expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect { notification.new_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1) end end end @@ -100,7 +97,7 @@ describe NotificationService do it { expect(notification.new_gpg_key(key)).to be_truthy } it 'sends email to key owner' do - expect{ notification.new_gpg_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect { notification.new_gpg_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1) end end end @@ -112,14 +109,14 @@ describe NotificationService do it { expect(notification.new_email(email)).to be_truthy } it 'sends email to email owner' do - expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1) end end end describe 'Notes' do context 'issue note' do - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:mentioned_issue) { create(:issue, assignees: issue.assignees) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @outsider also') } @@ -137,12 +134,11 @@ describe NotificationService do describe '#new_note' do it do add_users_with_subscription(note.project, issue) + reset_delivered_emails! # Ensure create SentNotification by noteable = issue 6 times, not noteable = note expect(SentNotification).to receive(:record).with(issue, any_args).exactly(8).times - reset_delivered_emails! - notification.new_note(note) should_email(@u_watcher) @@ -165,9 +161,10 @@ describe NotificationService do it "emails the note author if they've opted into notifications about their activity" do add_users_with_subscription(note.project, issue) - note.author.notified_of_own_activity = true reset_delivered_emails! + note.author.notified_of_own_activity = true + notification.new_note(note) should_email(note.author) @@ -228,7 +225,7 @@ describe NotificationService do end context 'confidential issue note' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:author) { create(:user) } let(:assignee) { create(:user) } let(:non_member) { create(:user) } @@ -260,7 +257,7 @@ describe NotificationService do end context 'issue note mention' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:mentioned_issue) { create(:issue, assignees: issue.assignees) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') } @@ -303,12 +300,17 @@ describe NotificationService do end context 'project snippet note' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:snippet) { create(:project_snippet, project: project, author: create(:user)) } let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') } before do build_team(note.project) + + # make sure these users can read the project snippet! + project.add_guest(@u_guest_watcher) + project.add_guest(@u_guest_custom) + note.project.add_master(note.author) reset_delivered_emails! end @@ -464,8 +466,8 @@ describe NotificationService do describe 'Issues' do let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } - let(:another_project) { create(:empty_project, :public, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } + let(:another_project) { create(:project, :public, namespace: group) } let(:issue) { create :issue, project: project, assignees: [assignee], description: 'cc @participant' } before do @@ -855,7 +857,7 @@ describe NotificationService do describe 'Merge Requests' do let(:group) { create(:group) } let(:project) { create(:project, :public, :repository, namespace: group) } - let(:another_project) { create(:empty_project, :public, namespace: group) } + let(:another_project) { create(:project, :public, namespace: group) } let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user), description: 'cc @participant' } before do @@ -1150,7 +1152,7 @@ describe NotificationService do end describe 'Projects' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do build_team(project) @@ -1211,7 +1213,7 @@ describe NotificationService do describe 'ProjectMember' do describe '#decline_group_invite' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:member) { create(:user) } before(:each) do @@ -1229,7 +1231,7 @@ describe NotificationService do end context 'guest user in private project' do - let(:private_project) { create(:empty_project, :private) } + let(:private_project) { create(:project, :private) } let(:guest) { create(:user) } let(:developer) { create(:user) } let(:assignee) { create(:user) } diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index 4fd9cb23ae1..64a9559791f 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PreviewMarkdownService do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } before do project.add_developer(user) diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index fc7238862ab..426593be428 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -8,7 +8,7 @@ describe Projects::AutocompleteService do let(:non_member) { create(:user) } let(:member) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let!(:issue) { create(:issue, project: project, title: 'Issue 1') } let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) } @@ -88,4 +88,31 @@ describe Projects::AutocompleteService do end end end + + describe '#milestones' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let!(:group_milestone) { create(:milestone, group: group) } + let!(:project_milestone) { create(:milestone, project: project) } + + let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) } + + it 'includes project and group milestones' do + expect(milestone_titles).to eq([group_milestone.title, project_milestone.title]) + end + + it 'does not include closed milestones' do + group_milestone.close + + expect(milestone_titles).to eq([project_milestone.title]) + end + + it 'does not include milestones from other projects in the group' do + other_project = create(:project, group: group) + project_milestone.update!(project: other_project) + + expect(milestone_titles).to eq([group_milestone.title]) + end + end end diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb new file mode 100644 index 00000000000..9919ec254c6 --- /dev/null +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Projects::CreateFromTemplateService do + let(:user) { create(:user) } + let(:project_params) do + { + path: user.to_param, + template_name: 'rails' + } + end + + subject { described_class.new(user, project_params) } + + it 'calls the importer service' do + expect_any_instance_of(Projects::GitlabProjectsImportService).to receive(:execute) + + subject.execute + end + + it 'returns the project thats created' do + project = subject.execute + + expect(project).to be_saved + expect(project.scheduled?).to be(true) + end +end diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index 701f6cc8c6a..da236052ebf 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -4,7 +4,7 @@ describe Projects::DownloadService do describe 'File service' do before do @user = create(:user) - @project = create(:empty_project, creator_id: @user.id, namespace: @user.namespace) + @project = create(:project, creator_id: @user.id, namespace: @user.namespace) end context 'for a URL that is not on whitelist' do diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb index 9b8c24ba112..835dae68fcd 100644 --- a/spec/services/projects/enable_deploy_key_service_spec.rb +++ b/spec/services/projects/enable_deploy_key_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::EnableDeployKeyService do let(:deploy_key) { create(:deploy_key, public: true) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { project.creator} let!(:params) { { key_id: deploy_key.id } } diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 67fe610f92a..034065aab00 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ImportService do - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:user) { project.creator } subject { described_class.new(project, user) } @@ -26,7 +26,7 @@ describe Projects::ImportService do result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The repository could not be created." + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - The repository could not be created." end end @@ -38,8 +38,7 @@ describe Projects::ImportService do context 'with a Github repository' do it 'succeeds if repository import is successfully' do - expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) + expect_any_instance_of(Github::Import).to receive(:execute).and_return(true) result = subject.execute @@ -52,16 +51,7 @@ describe Projects::ImportService do result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository" - end - - it 'does not remove the GitHub remote' do - expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) - - subject.execute - - expect(project.repository.raw_repository.remote_names).to include('github') + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The remote data could not be imported." end end @@ -86,7 +76,7 @@ describe Projects::ImportService do result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository" + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - Failed to import the repository" end end end @@ -102,8 +92,7 @@ describe Projects::ImportService do end it 'succeeds if importer succeeds' do - allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) + allow_any_instance_of(Github::Import).to receive(:execute).and_return(true) result = subject.execute @@ -111,10 +100,7 @@ describe Projects::ImportService do end it 'flushes various caches' do - allow_any_instance_of(Repository).to receive(:fetch_remote) - .and_return(true) - - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute) + allow_any_instance_of(Github::Import).to receive(:execute) .and_return(true) expect_any_instance_of(Repository).to receive(:expire_content_cache) @@ -123,29 +109,27 @@ describe Projects::ImportService do end it 'fails if importer fails' do - allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false) + allow_any_instance_of(Github::Import).to receive(:execute).and_return(false) result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The remote data could not be imported." + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - The remote data could not be imported." end it 'fails if importer raise an error' do - allow_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API')) + allow_any_instance_of(Github::Import).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API')) result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Github: failed to connect API" + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - Github: failed to connect API" end it 'expires content cache after error' do - allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false, true) + allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false) - expect_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new('Failed to import the repository')) + expect_any_instance_of(Repository).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new) expect_any_instance_of(Repository).to receive(:expire_content_cache) subject.execute diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 8777e63a101..0d18ceb8ff8 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::ParticipantsService do describe '#groups' do describe 'avatar_url' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png')) } let(:user) { create(:user) } let!(:group_member) { create(:group_member, group: group, user: user) } diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb index 2763437f184..f4c59735c43 100644 --- a/spec/services/projects/propagate_service_template_spec.rb +++ b/spec/services/projects/propagate_service_template_spec.rb @@ -15,7 +15,7 @@ describe Projects::PropagateServiceTemplate do }) end - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } it 'creates services for projects' do expect(project.pushover_service).to be_nil @@ -76,7 +76,7 @@ describe Projects::PropagateServiceTemplate do before do stub_const 'Projects::PropagateServiceTemplate::BATCH_SIZE', 3 - project_total.times { create(:empty_project) } + project_total.times { create(:project) } described_class.propagate(service_template) end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 36db1aab557..2cb60cbcfc4 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -59,7 +59,7 @@ describe Projects::TransferService do end def project_path(project) - File.join(project.repository_storage_path, "#{project.path_with_namespace}.git") + File.join(project.repository_storage_path, "#{project.disk_path}.git") end def current_path diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index 42925e73978..e4d4e6ff3dd 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::UpdatePagesConfigurationService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } subject { described_class.new(project) } describe "#update" do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d7b0df9a671..1b282e82187 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -5,7 +5,7 @@ describe Projects::UpdateService, '#execute' do let(:admin) { create(:admin) } let(:project) do - create(:empty_project, creator: user, namespace: user.namespace) + create(:project, creator: user, namespace: user.namespace) end context 'when changing visibility level' do @@ -59,7 +59,7 @@ describe Projects::UpdateService, '#execute' do end describe 'when updating project that has forks' do - let(:project) { create(:empty_project, :internal) } + let(:project) { create(:project, :internal) } let(:forked_project) { create(:forked_project_with_submodules, :internal) } before do @@ -101,6 +101,13 @@ describe Projects::UpdateService, '#execute' do expect(Project.find(project.id).default_branch).to eq 'feature' end + + it 'does not change a default branch' do + # The branch 'unexisted-branch' does not exist. + update_project(project, admin, default_branch: 'unexisted-branch') + + expect(Project.find(project.id).default_branch).to eq 'master' + end end context 'when updating a project that contains container images' do diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb index 592f9b5929e..835e83d6dba 100644 --- a/spec/services/protected_branches/create_service_spec.rb +++ b/spec/services/protected_branches/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProtectedBranches::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { project.owner } let(:params) do { diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb index 5698101af54..9fa5983db66 100644 --- a/spec/services/protected_branches/update_service_spec.rb +++ b/spec/services/protected_branches/update_service_spec.rb @@ -19,7 +19,7 @@ describe ProtectedBranches::UpdateService do let(:user) { create(:user) } it "raises error" do - expect{ service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError) + expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError) end end end diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb index 3a3cc9c1573..c3ed95aaebf 100644 --- a/spec/services/protected_tags/create_service_spec.rb +++ b/spec/services/protected_tags/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProtectedTags::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { project.owner } let(:params) do { diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb index d333430088d..2ece4e3b07b 100644 --- a/spec/services/protected_tags/update_service_spec.rb +++ b/spec/services/protected_tags/update_service_spec.rb @@ -19,7 +19,7 @@ describe ProtectedTags::UpdateService do let(:user) { create(:user) } it "raises error" do - expect{ service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError) + expect { service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError) end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 92bde2c92bf..30fa0ee6873 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe QuickActions::InterpretService do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:developer) { create(:user) } let(:developer2) { create(:user) } let(:issue) { create(:issue, project: project) } @@ -424,6 +424,26 @@ describe QuickActions::InterpretService do end end + context 'assign command with me alias' do + let(:content) { "/assign me" } + + context 'Issue' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do + _, updates = service.execute(content, issue) + + expect(updates).to eq(assignee_ids: [developer.id]) + end + end + + context 'Merge Request' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do + _, updates = service.execute(content, merge_request) + + expect(updates).to eq(assignee_ids: [developer.id]) + end + end + end + it_behaves_like 'empty command' do let(:content) { '/assign @abcd1234' } let(:issuable) { issue } @@ -683,7 +703,7 @@ describe QuickActions::InterpretService do context 'cross project references' do it_behaves_like 'duplicate command' do - let(:other_project) { create(:empty_project, :public) } + let(:other_project) { create(:project, :public) } let(:issue_duplicate) { create(:issue, project: other_project) } let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" } let(:issuable) { issue } @@ -695,7 +715,7 @@ describe QuickActions::InterpretService do end it_behaves_like 'empty command' do - let(:other_project) { create(:empty_project, :private) } + let(:other_project) { create(:project, :private) } let(:issue_duplicate) { create(:issue, project: other_project) } let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" } diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb index de921573b1a..1309240b430 100644 --- a/spec/services/search/global_service_spec.rb +++ b/spec/services/search/global_service_spec.rb @@ -4,10 +4,10 @@ describe Search::GlobalService do let(:user) { create(:user) } let(:internal_user) { create(:user) } - let!(:found_project) { create(:empty_project, :private, name: 'searchable_project') } - let!(:unfound_project) { create(:empty_project, :private, name: 'unfound_project') } - let!(:internal_project) { create(:empty_project, :internal, name: 'searchable_internal_project') } - let!(:public_project) { create(:empty_project, :public, name: 'searchable_public_project') } + let!(:found_project) { create(:project, :private, name: 'searchable_project') } + let!(:unfound_project) { create(:project, :private, name: 'unfound_project') } + let!(:internal_project) { create(:project, :internal, name: 'searchable_internal_project') } + let!(:public_project) { create(:project, :public, name: 'searchable_public_project') } before do found_project.add_master(user) diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb index cb3f02d2883..cbc553a60cf 100644 --- a/spec/services/search/group_service_spec.rb +++ b/spec/services/search/group_service_spec.rb @@ -8,14 +8,14 @@ describe Search::GroupService do let(:nested_group) { create(:group, :nested) } # These projects shouldn't be found - let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") } - let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )} - let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) } + let!(:outside_project) { create(:project, :public, name: "Outside #{term}") } + let!(:private_project) { create(:project, :private, namespace: nested_group, name: "Private #{term}" )} + let!(:other_project) { create(:project, :public, namespace: nested_group, name: term.reverse) } # These projects should be found - let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") } - let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") } - let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") } + let!(:project1) { create(:project, :internal, namespace: nested_group, name: "Inner #{term} 1") } + let!(:project2) { create(:project, :internal, namespace: nested_group, name: "Inner #{term} 2") } + let!(:project3) { create(:project, :internal, namespace: nested_group.parent, name: "Outer #{term}") } let(:results) { described_class.new(user, search_group, search: term).execute } subject { results.objects('projects') } diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb index 69438a3fa36..eae9bd4f5cf 100644 --- a/spec/services/search/snippet_service_spec.rb +++ b/spec/services/search/snippet_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Search::SnippetService do let(:author) { create(:author) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index a6ef7561bc8..02de83a2df8 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -7,13 +7,13 @@ describe SearchService do let(:inaccessible_group) { create(:group, :private) } let!(:group_member) { create(:group_member, group: accessible_group, user: user) } - let!(:accessible_project) { create(:empty_project, :private, name: 'accessible_project') } - let!(:inaccessible_project) { create(:empty_project, :private, name: 'inaccessible_project') } + let!(:accessible_project) { create(:project, :private, name: 'accessible_project') } + let!(:inaccessible_project) { create(:project, :private, name: 'inaccessible_project') } let(:note) { create(:note_on_issue, project: accessible_project) } let(:snippet) { create(:snippet, author: user) } - let(:group_project) { create(:empty_project, group: accessible_group, name: 'group_project') } - let(:public_project) { create(:empty_project, :public, name: 'public_project') } + let(:group_project) { create(:project, group: accessible_group, name: 'group_project') } + let(:public_project) { create(:project, :public, name: 'public_project') } before do accessible_project.add_master(user) @@ -28,7 +28,7 @@ describe SearchService do end it 'returns the project for guests' do - search_project = create :empty_project + search_project = create :project search_project.add_guest(user) project = described_class.new(user, project_id: search_project.id).project diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb index 46349c3e951..a14dfa3f01f 100644 --- a/spec/services/spam_service_spec.rb +++ b/spec/services/spam_service_spec.rb @@ -15,7 +15,7 @@ describe SpamService do end context 'when recaptcha was not verified' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let(:request) { double(:request, env: {}) } diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb index 817fa4262d5..c8a6fc1a99b 100644 --- a/spec/services/submit_usage_ping_service_spec.rb +++ b/spec/services/submit_usage_ping_service_spec.rb @@ -46,6 +46,8 @@ describe SubmitUsagePingService do .by(1) expect(ConversationalDevelopmentIndex::Metric.last.leader_issues).to eq 10.2 + expect(ConversationalDevelopmentIndex::Metric.last.instance_issues).to eq 3.2 + expect(ConversationalDevelopmentIndex::Metric.last.percentage_issues).to eq 31.37 end end @@ -60,6 +62,7 @@ describe SubmitUsagePingService do conv_index: { leader_issues: 10.2, instance_issues: 3.2, + percentage_issues: 31.37, leader_notes: 25.3, instance_notes: 23.2, @@ -86,7 +89,9 @@ describe SubmitUsagePingService do instance_projects_prometheus_active: 0.30, leader_service_desk_issues: 15.8, - instance_service_desk_issues: 15.1 + instance_service_desk_issues: 15.1, + + non_existing_column: 'value' } } end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index b2d9862e71e..8b5d9187785 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe SystemHooksService do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:project_member) { create(:project_member) } let(:key) { create(:key, user: user) } let(:deploy_key) { create(:key) } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index cb59493c343..6d36affa9dc 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe SystemNoteService do include Gitlab::Routing - let(:project) { create(:empty_project) } + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } let(:author) { create(:user) } let(:noteable) { create(:issue, project: project) } let(:issue) { noteable } @@ -34,7 +35,7 @@ describe SystemNoteService do context 'metadata' do it 'creates a new system note metadata record' do - expect { subject }.to change{ SystemNoteMetadata.count }.from(0).to(1) + expect { subject }.to change { SystemNoteMetadata.count }.from(0).to(1) end it 'creates a record correctly' do @@ -242,25 +243,51 @@ describe SystemNoteService do end describe '.change_milestone' do - subject { described_class.change_milestone(noteable, project, author, milestone) } + context 'for a project milestone' do + subject { described_class.change_milestone(noteable, project, author, milestone) } - let(:milestone) { create(:milestone, project: project) } + let(:milestone) { create(:milestone, project: project) } - it_behaves_like 'a system note' do - let(:action) { 'milestone' } - end + it_behaves_like 'a system note' do + let(:action) { 'milestone' } + end - context 'when milestone added' do - it 'sets the note text' do - expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" + context 'when milestone added' do + it 'sets the note text' do + expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" + end + end + + context 'when milestone removed' do + let(:milestone) { nil } + + it 'sets the note text' do + expect(subject.note).to eq 'removed milestone' + end end end - context 'when milestone removed' do - let(:milestone) { nil } + context 'for a group milestone' do + subject { described_class.change_milestone(noteable, project, author, milestone) } - it 'sets the note text' do - expect(subject.note).to eq 'removed milestone' + let(:milestone) { create(:milestone, group: group) } + + it_behaves_like 'a system note' do + let(:action) { 'milestone' } + end + + context 'when milestone added' do + it 'sets the note text to use the milestone name' do + expect(subject.note).to eq "changed milestone to #{milestone.to_reference(format: :name)}" + end + end + + context 'when milestone removed' do + let(:milestone) { nil } + + it 'sets the note text' do + expect(subject.note).to eq 'removed milestone' + end end end end @@ -450,7 +477,7 @@ describe SystemNoteService do end it 'does not create a system note metadata record' do - expect { subject }.not_to change{ SystemNoteMetadata.count } + expect { subject }.not_to change { SystemNoteMetadata.count } end end @@ -648,7 +675,7 @@ describe SystemNoteService do end describe '.noteable_moved' do - let(:new_project) { create(:empty_project) } + let(:new_project) { create(:project) } let(:new_noteable) { create(:issue, project: new_project) } subject do @@ -667,7 +694,7 @@ describe SystemNoteService do end it 'mentions referenced project' do - expect(subject.note).to include new_project.path_with_namespace + expect(subject.note).to include new_project.full_path end end @@ -718,7 +745,7 @@ describe SystemNoteService do describe 'JIRA integration' do include JiraServiceHelper - let(:project) { create(:jira_project) } + let(:project) { create(:jira_project, :repository) } let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, :simple, target_project: project, source_project: project) } @@ -873,7 +900,7 @@ describe SystemNoteService do describe "existing reference" do before do allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) - message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'" + message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.full_path}|http://localhost/#{project.full_path}/commit/#{commit.id}]:\n'#{commit.title.chomp}'" allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)]) end @@ -1116,7 +1143,7 @@ describe SystemNoteService do end context 'across different projects' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:canonical_issue) { create(:issue, project: other_project) } it_behaves_like 'a system note' do @@ -1141,7 +1168,7 @@ describe SystemNoteService do end context 'across different projects' do - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let(:duplicate_issue) { create(:issue, project: other_project) } it_behaves_like 'a system note' do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 230e40de9e0..a9b34a5258a 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -10,7 +10,7 @@ describe TodoService do let(:john_doe) { create(:user) } let(:skipped) { create(:user) } let(:skip_users) { [skipped] } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') } let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') } let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin, skipped].map(&:to_reference).join(' ') } @@ -103,7 +103,7 @@ describe TodoService do context 'when a private group is mentioned' do let(:group) { create(:group, :private) } - let(:project) { create(:empty_project, :private, group: group) } + let(:project) { create(:project, :private, group: group) } let(:issue) { create(:issue, author: author, project: project, description: group.to_reference) } before do @@ -336,7 +336,7 @@ describe TodoService do describe '#mark_todos_as_done' do it_behaves_like 'updating todos state', :mark_todos_as_done, :pending, :done do - let(:collection) { [first_todo, second_todo] } + let(:collection) { Todo.all } end end @@ -348,7 +348,7 @@ describe TodoService do describe '#mark_todos_as_pending' do it_behaves_like 'updating todos state', :mark_todos_as_pending, :done, :pending do - let(:collection) { [first_todo, second_todo] } + let(:collection) { Todo.all } end end @@ -586,13 +586,13 @@ describe TodoService do it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author) - expect{ service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count) + expect { service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count) end it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr_assigned, author: author) - expect{ service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count) + expect { service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count) end context 'with a task list' do @@ -880,14 +880,16 @@ describe TodoService do it 'marks an array of todos as done' do todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) - expect { described_class.new.mark_todos_as_done([todo], john_doe) } + todos = TodosFinder.new(john_doe, {}).execute + expect { described_class.new.mark_todos_as_done(todos, john_doe) } .to change { todo.reload.state }.from('pending').to('done') end it 'returns the ids of updated todos' do # Needed on API todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) - expect(described_class.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id]) + todos = TodosFinder.new(john_doe, {}).execute + expect(described_class.new.mark_todos_as_done(todos, john_doe)).to eq([todo.id]) end context 'when some of the todos are done already' do @@ -907,11 +909,32 @@ describe TodoService do expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([]) end end + end + + describe '#mark_todos_as_done_by_ids' do + let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } + let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } + + it 'marks an array of todo ids as done' do + todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) + another_todo = create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) + + expect { described_class.new.mark_todos_as_done_by_ids([todo.id, another_todo.id], john_doe) } + .to change { john_doe.todos.done.count }.from(0).to(2) + end + + it 'marks a single todo id as done' do + todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) + + expect { described_class.new.mark_todos_as_done_by_ids(todo.id, john_doe) } + .to change { todo.reload.state }.from('pending').to('done') + end it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do create(:todo, :mentioned, user: john_doe, target: issue, project: project) todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) - described_class.new.mark_todos_as_done([todo], john_doe) + + described_class.new.mark_todos_as_done_by_ids(todo, john_doe) expect_any_instance_of(TodosFinder).not_to receive(:execute) diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb index cf76a18f171..24f3a5c5ff0 100644 --- a/spec/services/upload_service_spec.rb +++ b/spec/services/upload_service_spec.rb @@ -4,7 +4,7 @@ describe UploadService do describe 'File service' do before do @user = create(:user) - @project = create(:empty_project, creator_id: @user.id, namespace: @user.namespace) + @project = create(:project, creator_id: @user.id, namespace: @user.namespace) end context 'for valid gif file' do diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 786335120fd..a82567f6f43 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -5,7 +5,7 @@ describe Users::DestroyService do let!(:user) { create(:user) } let!(:admin) { create(:admin) } let!(:namespace) { create(:namespace, owner: user) } - let!(:project) { create(:empty_project, namespace: namespace) } + let!(:project) { create(:project, namespace: namespace) } let(:service) { described_class.new(admin) } context 'no options are given' do @@ -66,7 +66,7 @@ describe Users::DestroyService do end context "a deleted user's merge_requests" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.add_developer(user) diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb index a0030ce8809..ac3a8738cac 100644 --- a/spec/services/users/migrate_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Users::MigrateToGhostUserService do let!(:user) { create(:user) } - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:service) { described_class.new(user) } context "migrating a user's associated records to the ghost user" do diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 1c0f55d2965..08fd26d67fd 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Users::RefreshAuthorizedProjectsService do # We're using let! here so that any expectations for the service class are not # triggered twice. - let!(:project) { create(:empty_project) } + let!(:project) { create(:project) } let(:user) { project.namespace.owner } let(:service) { described_class.new(user) } @@ -28,7 +28,7 @@ describe Users::RefreshAuthorizedProjectsService do end it 'updates the authorized projects of the user' do - project2 = create(:empty_project) + project2 = create(:project) to_remove = user.project_authorizations .create!(project: project2, access_level: Gitlab::Access::MASTER) @@ -109,7 +109,7 @@ describe Users::RefreshAuthorizedProjectsService do end context 'projects the user is a member of' do - let!(:other_project) { create(:empty_project) } + let!(:other_project) { create(:project) } before do other_project.team.add_reporter(user) @@ -122,7 +122,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects of groups the user is a member of' do let(:group) { create(:group) } - let!(:other_project) { create(:empty_project, group: group) } + let!(:other_project) { create(:project, group: group) } before do group.add_owner(user) @@ -136,7 +136,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects of subgroups of groups the user is a member of', :nested_groups do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } - let!(:other_project) { create(:empty_project, group: nested_group) } + let!(:other_project) { create(:project, group: nested_group) } before do group.add_master(user) @@ -149,7 +149,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects shared with groups the user is a member of' do let(:group) { create(:group) } - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let!(:project_group_link) { create(:project_group_link, project: other_project, group: group, group_access: Gitlab::Access::GUEST) } before do @@ -164,7 +164,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects shared with subgroups of groups the user is a member of', :nested_groups do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } - let(:other_project) { create(:empty_project) } + let(:other_project) { create(:project) } let!(:project_group_link) { create(:project_group_link, project: other_project, group: nested_group, group_access: Gitlab::Access::DEVELOPER) } before do diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index e79c12daa1c..79d90defd78 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe WebHookService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:project_hook) { create(:project_hook) } let(:headers) do { @@ -112,6 +112,23 @@ describe WebHookService do end end + context 'with unsafe response body' do + before do + WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "\xBB") + service_instance.execute + end + + it 'log successful execution' do + expect(hook_log.trigger).to eq('push_hooks') + expect(hook_log.url).to eq(project_hook.url) + expect(hook_log.request_headers).to eq(headers) + expect(hook_log.response_body).to eq('') + expect(hook_log.response_status).to eq('200') + expect(hook_log.execution_duration).to be > 0 + expect(hook_log.internal_error_message).to be_nil + end + end + context 'should not log ServiceHooks' do let(:service_hook) { create(:service_hook) } let(:service_instance) { described_class.new(service_hook, data, 'service_hook') } diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb index fa3863e9b30..b270194d9b8 100644 --- a/spec/services/wiki_pages/create_service_spec.rb +++ b/spec/services/wiki_pages/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe WikiPages::CreateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:opts) do diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb index 1668cd00ca8..2938126914b 100644 --- a/spec/services/wiki_pages/destroy_service_spec.rb +++ b/spec/services/wiki_pages/destroy_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe WikiPages::DestroyService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:page) { create(:wiki_page) } diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb index a672c84034b..2399db7d3d4 100644 --- a/spec/services/wiki_pages/update_service_spec.rb +++ b/spec/services/wiki_pages/update_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe WikiPages::UpdateService do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:page) { create(:wiki_page) } @@ -9,7 +9,8 @@ describe WikiPages::UpdateService do { content: 'New content for wiki page', format: 'markdown', - message: 'New wiki message' + message: 'New wiki message', + title: 'New Title' } end @@ -27,6 +28,7 @@ describe WikiPages::UpdateService do expect(updated_page.message).to eq(opts[:message]) expect(updated_page.content).to eq(opts[:content]) expect(updated_page.format).to eq(opts[:format].to_sym) + expect(updated_page.title).to eq(opts[:title]) end it 'executes webhooks' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 85335643921..0ba6ed56314 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -49,7 +49,7 @@ RSpec.configure do |config| config.include SearchHelpers, type: :feature config.include WaitForRequests, :js config.include StubConfiguration - config.include EmailHelpers, type: :mailer + config.include EmailHelpers, :mailer, type: :mailer config.include TestEnv config.include ActiveJob::TestHelper config.include ActiveSupport::Testing::TimeHelpers @@ -93,6 +93,10 @@ RSpec.configure do |config| RequestStore.clear! end + config.before(:example, :mailer) do + reset_delivered_emails! + end + if ENV['CI'] config.around(:each) do |ex| ex.run_with_retry retry: 2 @@ -129,10 +133,14 @@ RSpec.configure do |config| config.before(:example, :migration) do ActiveRecord::Migrator .migrate(migrations_paths, previous_migration.version) + + reset_column_in_migration_models end config.after(:example, :migration) do ActiveRecord::Migrator.migrate(migrations_paths) + + reset_column_in_migration_models end config.around(:each, :nested_groups) do |example| diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 480e7d5151f..4bb5113957e 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -50,7 +50,7 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) - expect(json_response.map{ |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) + expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) end it 'does not return any milestone if none found' do @@ -239,7 +239,7 @@ shared_examples_for 'group and project milestones' do |route_definition| end describe 'confidential issues' do - let!(:public_project) { create(:empty_project, :public) } + let!(:public_project) { create(:project, :public) } let!(:context_group) { try(:group) } let!(:milestone) do context_group ? create(:milestone, group: context_group) : create(:milestone, project: public_project) diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb index 67599f77adb..6591d56e473 100644 --- a/spec/support/api/schema_matcher.rb +++ b/spec/support/api/schema_matcher.rb @@ -1,23 +1,25 @@ -def schema_path(schema) - schema_directory = "#{Dir.pwd}/spec/fixtures/api/schemas" - "#{schema_directory}/#{schema}.json" +module SchemaPath + def self.expand(schema, dir = '') + Rails.root.join('spec', dir, "fixtures/api/schemas/#{schema}.json").to_s + end end -RSpec::Matchers.define :match_response_schema do |schema, **options| +RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options| match do |response| - @errors = JSON::Validator.fully_validate(schema_path(schema), response.body, options) + @errors = JSON::Validator.fully_validate( + SchemaPath.expand(schema, dir), response.body, options) @errors.empty? end failure_message do |response| - "didn't match the schema defined by #{schema_path(schema)}" \ + "didn't match the schema defined by #{SchemaPath.expand(schema, dir)}" \ " The validation errors were:\n#{@errors.join("\n")}" end end -RSpec::Matchers.define :match_schema do |schema, **options| +RSpec::Matchers.define :match_schema do |schema, dir: '', **options| match do |data| - JSON::Validator.validate!(schema_path(schema), data, options) + JSON::Validator.validate!(SchemaPath.expand(schema, dir), data, options) end end diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb index 978b0b9cc30..dc97a39f051 100644 --- a/spec/support/chat_slash_commands_shared_examples.rb +++ b/spec/support/chat_slash_commands_shared_examples.rb @@ -36,7 +36,7 @@ RSpec.shared_examples 'chat slash commands service' do end context 'with a token passed' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:params) { { token: 'token' } } before do diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index a8d9566b4e4..4eec3127464 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -56,7 +56,7 @@ shared_examples 'a GitHub-ish import controller: GET status' do end it "assigns variables" do - project = create(:empty_project, import_type: provider, creator_id: user.id) + project = create(:project, import_type: provider, creator_id: user.id) stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo]) get :status @@ -69,7 +69,7 @@ shared_examples 'a GitHub-ish import controller: GET status' do end it "does not show already added project" do - project = create(:empty_project, import_type: provider, creator_id: user.id, import_source: 'asd/vim') + project = create(:project, import_type: provider, creator_id: user.id, import_source: 'asd/vim') stub_client(repos: [repo], orgs: []) get :status diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index c0a5491a430..30911e7fa86 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -41,7 +41,9 @@ module CycleAnalyticsHelpers target_branch: 'master' } - MergeRequests::CreateService.new(project, user, opts).execute + mr = MergeRequests::CreateService.new(project, user, opts).execute + NewMergeRequestWorker.new.perform(mr, user) + mr end def merge_merge_requests_closing_issue(issue) diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 035428a7d9b..68f0ce8afb3 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -5,7 +5,14 @@ shared_examples 'issuable record that supports quick actions in its description include QuickActionsHelpers let(:master) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) do + case issuable_type + when :merge_request + create(:project, :public, :repository) + when :issue + create(:project, :public) + end + end let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let!(:label_bug) { create(:label, project: project, title: 'bug') } let!(:label_feature) { create(:label, project: project, title: 'feature') } @@ -272,6 +279,17 @@ shared_examples 'issuable record that supports quick actions in its description expect(issuable.subscribed?(master, project)).to be_falsy end end + + context "with a note assigning the #{issuable_type} to the current user" do + it "assigns the #{issuable_type} to the current user" do + write_note("/assign me") + + expect(page).not_to have_content '/assign me' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload.assignees).to eq [master] + end + end end describe "preview of note on #{issuable_type}" do diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 57b6abe12b7..2011408be93 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -6,7 +6,7 @@ module ExportFileHelper ObjectWithParent = Struct.new(:object, :parent, :key_found) def setup_project - project = create(:project, :public) + project = create(:project, :public, :repository) create(:release, project: project) diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb index 970fe10db2b..42f3b4db23c 100644 --- a/spec/support/issuable_shared_examples.rb +++ b/spec/support/issuable_shared_examples.rb @@ -21,15 +21,15 @@ shared_examples 'system notes for milestones' do create(:group_member, group: group, user: user) end - it 'does not create system note' do + it 'creates a system note' do expect do update_issuable(milestone: group_milestone) - end.not_to change { Note.system.count } + end.to change { Note.system.count }.by(1) end end context 'project milestones' do - it 'creates system note' do + it 'creates a system note' do expect do update_issuable(milestone: create(:milestone)) end.to change { Note.system.count }.by(1) diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb index 3406e4c3161..a60d3b0d22d 100644 --- a/spec/support/issuables_list_metadata_shared_examples.rb +++ b/spec/support/issuables_list_metadata_shared_examples.rb @@ -11,10 +11,6 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| end @issuable_ids << issuable.id - - issuable.id.times { create(:note, noteable: issuable, project: issuable.project) } - (issuable.id + 1).times { create(:award_emoji, :downvote, awardable: issuable) } - (issuable.id + 2).times { create(:award_emoji, :upvote, awardable: issuable) } end end @@ -27,15 +23,14 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| meta_data = assigns(:issuable_meta_data) - @issuable_ids.each do |id| - expect(meta_data[id].notes_count).to eq(id) - expect(meta_data[id].downvotes).to eq(id + 1) - expect(meta_data[id].upvotes).to eq(id + 2) + aggregate_failures do + expect(meta_data.keys).to match_array(@issuable_ids) + expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta)) end end describe "when given empty collection" do - let(:project2) { create(:empty_project, :public) } + let(:project2) { create(:project, :public) } it "doesn't execute any queries with false conditions" do get_action = diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index c714d1b08a6..3e117530151 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -1,3 +1,5 @@ +require_relative 'devise_helpers' + module LoginHelpers include DeviseHelpers diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index 21a054af4e1..c90359d7cfa 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -23,7 +23,7 @@ class MarkdownFeature # Direct references ---------------------------------------------------------- def project - @project ||= create(:project, :repository).tap do |project| + @project ||= create(:project, :repository, group: group).tap do |project| project.team << [user, :master] end end @@ -75,6 +75,10 @@ class MarkdownFeature @milestone ||= create(:milestone, name: 'next goal', project: project) end + def group_milestone + @group_milestone ||= create(:milestone, name: 'group-milestone', group: group) + end + # Cross-references ----------------------------------------------------------- def xproject diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 7afa57fb76b..d12b2757427 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -155,7 +155,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-milestone', count: 6) + expect(actual).to have_selector('a.gfm.gfm-milestone', count: 8) end end diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb index ac8c4ab91d9..bb0feca7c43 100644 --- a/spec/support/matchers/query_matcher.rb +++ b/spec/support/matchers/query_matcher.rb @@ -28,6 +28,6 @@ RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil| def query_count(regex, &block) @recorder = ActiveRecord::QueryRecorder.new(&block).log - @recorder.select{ |q| q.match(regex) } + @recorder.select { |q| q.match(regex) } end end diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb index 91fbb4eaf48..aabdad13047 100644 --- a/spec/support/migrations_helpers.rb +++ b/spec/support/migrations_helpers.rb @@ -15,6 +15,16 @@ module MigrationsHelpers ActiveRecord::Migrator.migrations(migrations_paths) end + def reset_column_in_migration_models + described_class.constants.sort.each do |name| + const = described_class.const_get(name) + + if const.is_a?(Class) && const < ActiveRecord::Base + const.reset_column_information + end + end + end + def previous_migration migrations.each_cons(2) do |previous, migration| break previous if migration.name == described_class.name diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb index 76411065265..136f92c6419 100644 --- a/spec/support/notify_shared_examples.rb +++ b/spec/support/notify_shared_examples.rb @@ -3,11 +3,10 @@ shared_context 'gitlab email notification' do let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } let(:recipient) { create(:user, email: 'recipient@example.com') } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:new_user_address) { 'newguy@example.com' } before do - reset_delivered_emails! email = recipient.emails.create(email: "notifications@example.com") recipient.update_attribute(:notification_email, email.email) stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}") @@ -50,7 +49,7 @@ shared_examples 'an email with X-GitLab headers containing project details' do aggregate_failures do is_expected.to have_header('X-GitLab-Project', /#{project.name}/) is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) - is_expected.to have_header('X-GitLab-Project-Path', /#{project.path_with_namespace}/) + is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) end end end diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/project_features_apply_to_issuables_shared_examples.rb index 81b51509e0b..639b0924197 100644 --- a/spec/support/project_features_apply_to_issuables_shared_examples.rb +++ b/spec/support/project_features_apply_to_issuables_shared_examples.rb @@ -5,7 +5,7 @@ shared_examples 'project features apply to issuables' do |klass| let(:user_in_group) { create(:group_member, :developer, user: create(:user), group: group ).user } let(:user_outside_group) { create(:user) } - let(:project) { create(:empty_project, :public, project_args) } + let(:project) { create(:project, :public, project_args) } def project_args feature = "#{described_class.model_name.plural}_access_level".to_sym diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb index 7dbaa6a6459..1eb405d4be8 100644 --- a/spec/support/project_hook_data_shared_example.rb +++ b/spec/support/project_hook_data_shared_example.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :pro expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo) expect(data[project_key][:namespace]).to eq(project.namespace.name) expect(data[project_key][:visibility_level]).to eq(project.visibility_level) - expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace) + expect(data[project_key][:path_with_namespace]).to eq(project.full_path) expect(data[project_key][:default_branch]).to eq(project.default_branch) expect(data[project_key][:homepage]).to eq(project.web_url) expect(data[project_key][:url]).to eq(project.url_to_repo) @@ -27,7 +27,7 @@ RSpec.shared_examples 'project hook data' do |project_key: :project| expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo) expect(data[project_key][:namespace]).to eq(project.namespace.name) expect(data[project_key][:visibility_level]).to eq(project.visibility_level) - expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace) + expect(data[project_key][:path_with_namespace]).to eq(project.full_path) expect(data[project_key][:default_branch]).to eq(project.default_branch) end end diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 016e16fc8d4..620fa37d455 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -10,11 +10,61 @@ RSpec.shared_examples 'additional metrics query' do [{ 'metric': {}, 'values': [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }] end + let(:client) { double('prometheus_client') } + let(:query_result) { described_class.new(client).query(*query_params) } + let(:environment) { create(:environment, slug: 'environment-slug') } + before do allow(client).to receive(:label_values).and_return(metric_names) allow(metric_group_class).to receive(:all).and_return([simple_metric_group(metrics: [simple_metric])]) end + context 'metrics query context' do + subject! { described_class.new(client) } + + shared_examples 'query context containing environment slug and filter' do + it 'contains ci_environment_slug' do + expect(subject).to receive(:query_metrics).with(hash_including(ci_environment_slug: environment.slug)) + + subject.query(*query_params) + end + + it 'contains environment filter' do + expect(subject).to receive(:query_metrics).with( + hash_including( + environment_filter: "container_name!=\"POD\",environment=\"#{environment.slug}\"" + ) + ) + + subject.query(*query_params) + end + end + + describe 'project has Kubernetes service' do + let(:project) { create(:kubernetes_project) } + let(:environment) { create(:environment, slug: 'environment-slug', project: project) } + let(:kube_namespace) { project.kubernetes_service.actual_namespace } + + it_behaves_like 'query context containing environment slug and filter' + + it 'query context contains kube_namespace' do + expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: kube_namespace)) + + subject.query(*query_params) + end + end + + describe 'project without Kubernetes service' do + it_behaves_like 'query context containing environment slug and filter' + + it 'query context contains empty kube_namespace' do + expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: '')) + + subject.query(*query_params) + end + end + end + context 'with one group where two metrics is found' do before do allow(metric_group_class).to receive(:all).and_return([simple_metric_group]) @@ -51,6 +101,7 @@ RSpec.shared_examples 'additional metrics query' do context 'with two groups with one metric each' do let(:metrics) { [simple_metric(queries: [simple_query])] } + before do allow(metric_group_class).to receive(:all).and_return( [ diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb index 5cb415111d2..86bfeed107c 100644 --- a/spec/support/rake_helpers.rb +++ b/spec/support/rake_helpers.rb @@ -5,11 +5,15 @@ module RakeHelpers end def stub_warn_user_is_not_gitlab - allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab) + allow(main_object).to receive(:warn_user_is_not_gitlab) end def silence_output - allow($stdout).to receive(:puts) - allow($stdout).to receive(:print) + allow(main_object).to receive(:puts) + allow(main_object).to receive(:print) + end + + def main_object + @main_object ||= TOPLEVEL_BINDING.eval('self') end end diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index 855051921f0..adfd256dff1 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -3,7 +3,14 @@ require "spec_helper" shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields| record_class_name = record_class.to_s.titleize.downcase - let(:project) { create(:project) } + let(:project) do + case record_class + when MergeRequest + create(:project, :repository) + else + create(:project) + end + end before do project.add_developer(user) diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb index df18926d58c..f3deae0f455 100644 --- a/spec/support/stored_repositories.rb +++ b/spec/support/stored_repositories.rb @@ -2,4 +2,16 @@ RSpec.configure do |config| config.before(:each, :repository) do TestEnv.clean_test_path end + + config.before(:all, :broken_storage) do + FileUtils.rm_rf Gitlab.config.repositories.storages.broken['path'] + end + + config.before(:each, :broken_storage) do + allow(Gitlab::GitalyClient).to receive(:call) do + raise GRPC::Unavailable.new('Gitaly broken in this spec') + end + + Gitlab::Git::Storage::CircuitBreaker.reset_all! + end end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index 516f8878679..37c89d37aa0 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -38,6 +38,17 @@ module StubConfiguration allow(Gitlab.config.backup).to receive_messages(to_settings(messages)) end + def stub_storage_settings(messages) + messages.each do |storage_name, storage_settings| + storage_settings['failure_count_threshold'] ||= 10 + storage_settings['failure_wait_time'] ||= 30 + storage_settings['failure_reset_time'] ||= 1800 + storage_settings['storage_timeout'] ||= 5 + end + + allow(Gitlab.config.repositories).to receive(:storages).and_return(messages) + end + private # Modifies stubbed messages to also stub possible predicate versions diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 86f9568c12e..1e39f80699c 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -122,32 +122,60 @@ module TestEnv end def setup_gitlab_shell - shell_needs_update = component_needs_update?(Gitlab.config.gitlab_shell.path, + puts "\n==> Setting up Gitlab Shell..." + start = Time.now + gitlab_shell_dir = Gitlab.config.gitlab_shell.path + shell_needs_update = component_needs_update?(gitlab_shell_dir, Gitlab::Shell.version_required) unless !shell_needs_update || system('rake', 'gitlab:shell:install') - raise 'Can`t clone gitlab-shell' + puts "\nGitLab Shell failed to install, cleaning up #{gitlab_shell_dir}!\n" + FileUtils.rm_rf(gitlab_shell_dir) + exit 1 end + + puts " GitLab Shell setup in #{Time.now - start} seconds...\n" end def setup_gitaly + puts "\n==> Setting up Gitaly..." + start = Time.now socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '') gitaly_dir = File.dirname(socket_path) + + if gitaly_dir_stale?(gitaly_dir) + puts " Gitaly is outdated, cleaning up #{gitaly_dir}!" + FileUtils.rm_rf(gitaly_dir) + end + gitaly_needs_update = component_needs_update?(gitaly_dir, Gitlab::GitalyClient.expected_server_version) unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]") - raise "Can't clone gitaly" + puts "\nGitaly failed to install, cleaning up #{gitaly_dir}!\n" + FileUtils.rm_rf(gitaly_dir) + exit 1 end start_gitaly(gitaly_dir) + puts " Gitaly setup in #{Time.now - start} seconds...\n" + end + + def gitaly_dir_stale?(dir) + gitaly_executable = File.join(dir, 'gitaly') + return false unless File.exist?(gitaly_executable) + + File.mtime(gitaly_executable) < File.mtime(Rails.root.join('GITALY_SERVER_VERSION')) end def start_gitaly(gitaly_dir) - gitaly_exec = File.join(gitaly_dir, 'gitaly') - gitaly_config = File.join(gitaly_dir, 'config.toml') - log_file = Rails.root.join('log/gitaly-test.log').to_s - @gitaly_pid = Bundler.with_original_env { spawn(gitaly_exec, gitaly_config, [:out, :err] => log_file) } + if ENV['CI'].present? + # Gitaly has been spawned outside this process already + return + end + + spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s + @gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i } end def stop_gitaly @@ -208,7 +236,6 @@ module TestEnv # Otherwise they'd be created by the first test, often timing out and # causing a transient test failure def eager_load_driver_server - return unless ENV['CI'] return unless defined?(Capybara) puts "Starting the Capybara driver server..." @@ -223,6 +250,14 @@ module TestEnv "#{forked_repo_path}_bare" end + def with_empty_bare_repository(name = nil) + path = Rails.root.join('tmp/tests', name || 'empty-bare-repository').to_s + + yield(Rugged::Repository.init_at(path, :bare)) + ensure + FileUtils.rm_rf(path) + end + private def factory_repo_path diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/updating_mentions_shared_examples.rb index eeec3e1d79b..565d3203e4f 100644 --- a/spec/support/updating_mentions_shared_examples.rb +++ b/spec/support/updating_mentions_shared_examples.rb @@ -7,8 +7,6 @@ RSpec.shared_examples 'updating mentions' do |service_class| end def update_mentionable(opts) - reset_delivered_emails! - perform_enqueued_jobs do service_class.new(project, user, opts).execute(mentionable) end diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_spec.rb index ed6c5b09663..5b01665019a 100644 --- a/spec/tasks/config_lint_spec.rb +++ b/spec/tasks/config_lint_spec.rb @@ -2,14 +2,14 @@ require 'rake_helper' Rake.application.rake_require 'tasks/config_lint' describe ConfigLint do - let(:files){ ['lib/support/fake.sh'] } + let(:files) { ['lib/support/fake.sh'] } it 'errors out if any bash scripts have errors' do - expect { described_class.run(files){ system('exit 1') } }.to raise_error(SystemExit) + expect { described_class.run(files) { system('exit 1') } }.to raise_error(SystemExit) end it 'passes if all scripts are fine' do - expect { described_class.run(files){ system('exit 0') } }.not_to raise_error + expect { described_class.run(files) { system('exit 0') } }.not_to raise_error end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 71580a788d0..fae92451b46 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -117,7 +117,7 @@ describe 'gitlab:app namespace rake task' do describe 'backup creation and deletion using custom_hooks' do let(:project) { create(:project, :repository) } - let(:user_backup_path) { "repositories/#{project.path_with_namespace}" } + let(:user_backup_path) { "repositories/#{project.disk_path}" } before(:each) do @origin_cd = Dir.pwd @@ -261,8 +261,8 @@ describe 'gitlab:app namespace rake task' do %W{tar -tvf #{@backup_tar} repositories} ) expect(exit_status).to eq(0) - expect(tar_contents).to match("repositories/#{project_a.path_with_namespace}.bundle") - expect(tar_contents).to match("repositories/#{project_b.path_with_namespace}.bundle") + expect(tar_contents).to match("repositories/#{project_a.disk_path}.bundle") + expect(tar_contents).to match("repositories/#{project_b.disk_path}.bundle") end end end # backup_create task diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index d42d2423f15..43ac1a72152 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -20,7 +20,7 @@ describe 'gitlab:gitaly namespace rake task' do context 'when an underlying Git command fail' do it 'aborts and display a help message' do - expect_any_instance_of(Object) + expect(main_object) .to receive(:checkout_or_clone_version).and_raise 'Git error' expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error' @@ -33,7 +33,7 @@ describe 'gitlab:gitaly namespace rake task' do end it 'calls checkout_or_clone_version with the right arguments' do - expect_any_instance_of(Object) + expect(main_object) .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path) run_rake_task('gitlab:gitaly:install', clone_path) @@ -41,6 +41,16 @@ describe 'gitlab:gitaly namespace rake task' do end describe 'gmake/make' do + let(:command_preamble) { %w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] } + + before(:all) do + @old_env_ci = ENV.delete('CI') + end + + after(:all) do + ENV['CI'] = @old_env_ci if @old_env_ci + end + before do FileUtils.mkdir_p(clone_path) expect(Dir).to receive(:chdir).with(clone_path).and_call_original @@ -48,13 +58,13 @@ describe 'gitlab:gitaly namespace rake task' do context 'gmake is available' do before do - expect_any_instance_of(Object).to receive(:checkout_or_clone_version) - allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + expect(main_object).to receive(:checkout_or_clone_version) + allow(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true) end it 'calls gmake in the gitaly directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) - expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + expect(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true) run_rake_task('gitlab:gitaly:install', clone_path) end @@ -62,13 +72,13 @@ describe 'gitlab:gitaly namespace rake task' do context 'gmake is not available' do before do - expect_any_instance_of(Object).to receive(:checkout_or_clone_version) - allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + expect(main_object).to receive(:checkout_or_clone_version) + allow(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true) end it 'calls make in the gitaly directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) - expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true) run_rake_task('gitlab:gitaly:install', clone_path) end @@ -97,6 +107,8 @@ describe 'gitlab:gitaly namespace rake task' do # Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)} # This is in TOML format suitable for use in Gitaly's config.toml file. socket_path = "/path/to/my.socket" + [gitlab-shell] + dir = "#{Gitlab.config.gitlab_shell.path}" [[storage]] name = "default" path = "/path/to/default" diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index ee3614c50f6..65155cb044d 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -22,7 +22,8 @@ describe 'gitlab:shell rake tasks' do describe 'create_hooks task' do it 'calls gitlab-shell bin/create_hooks' do expect_any_instance_of(Object).to receive(:system) - .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", *repository_storage_paths_args) + .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", + *Gitlab::TaskHelpers.repository_storage_paths_args) run_rake_task('gitlab:shell:create_hooks') end diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb index 1b68f3044a4..42516d36c67 100644 --- a/spec/tasks/gitlab/workhorse_rake_spec.rb +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -20,7 +20,7 @@ describe 'gitlab:workhorse namespace rake task' do context 'when an underlying Git command fail' do it 'aborts and display a help message' do - expect_any_instance_of(Object) + expect(main_object) .to receive(:checkout_or_clone_version).and_raise 'Git error' expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error' @@ -33,7 +33,7 @@ describe 'gitlab:workhorse namespace rake task' do end it 'calls checkout_or_clone_version with the right arguments' do - expect_any_instance_of(Object) + expect(main_object) .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path) run_rake_task('gitlab:workhorse:install', clone_path) @@ -48,13 +48,13 @@ describe 'gitlab:workhorse namespace rake task' do context 'gmake is available' do before do - expect_any_instance_of(Object).to receive(:checkout_or_clone_version) - allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + expect(main_object).to receive(:checkout_or_clone_version) + allow(Object).to receive(:run_command!).with(['gmake']).and_return(true) end it 'calls gmake in the gitlab-workhorse directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) - expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + expect(main_object).to receive(:run_command!).with(['gmake']).and_return(true) run_rake_task('gitlab:workhorse:install', clone_path) end @@ -62,13 +62,13 @@ describe 'gitlab:workhorse namespace rake task' do context 'gmake is not available' do before do - expect_any_instance_of(Object).to receive(:checkout_or_clone_version) - allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + expect(main_object).to receive(:checkout_or_clone_version) + allow(main_object).to receive(:run_command!).with(['make']).and_return(true) end it 'calls make in the gitlab-workhorse directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) - expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + expect(main_object).to receive(:run_command!).with(['make']).and_return(true) run_rake_task('gitlab:workhorse:install', clone_path) end diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 47e9365e13d..2492d56a5cf 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe FileUploader do - let(:uploader) { described_class.new(build_stubbed(:empty_project)) } + let(:uploader) { described_class.new(build_stubbed(:project)) } describe '.absolute_path' do it 'returns the correct absolute path by building it dynamically' do @@ -17,7 +17,7 @@ describe FileUploader do describe "#store_dir" do it "stores in the namespace path" do - project = build_stubbed(:empty_project) + project = build_stubbed(:project) uploader = described_class.new(project) expect(uploader.store_dir).to include(project.path_with_namespace) diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb index eb55e8ebd24..e505edc75ce 100644 --- a/spec/uploaders/personal_file_uploader_spec.rb +++ b/spec/uploaders/personal_file_uploader_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe PersonalFileUploader do - let(:uploader) { described_class.new(build_stubbed(:empty_project)) } + let(:uploader) { described_class.new(build_stubbed(:project)) } let(:snippet) { create(:personal_snippet) } describe '.absolute_path' do diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb index 8bd5306ff98..08e1c5a728a 100644 --- a/spec/validators/dynamic_path_validator_spec.rb +++ b/spec/validators/dynamic_path_validator_spec.rb @@ -86,7 +86,7 @@ describe DynamicPathValidator do end it 'updating to an invalid path is not allowed' do - project = create(:empty_project) + project = create(:project) project.path = 'update' validator.validate_each(project, :path, 'update') diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb index 68d2d72876e..df742bf6848 100644 --- a/spec/views/admin/dashboard/index.html.haml_spec.rb +++ b/spec/views/admin/dashboard/index.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'admin/dashboard/index.html.haml' do include Devise::Test::ControllerHelpers before do - assign(:projects, create_list(:empty_project, 1)) + assign(:projects, create_list(:project, 1)) assign(:users, create_list(:user, 1)) assign(:groups, create_list(:group, 1)) diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb index de0b59f83f8..49f57969239 100644 --- a/spec/views/ci/status/_badge.html.haml_spec.rb +++ b/spec/views/ci/status/_badge.html.haml_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'ci/status/_badge' do let(:user) { create(:user) } - let(:project) { create(:empty_project, :private) } + let(:project) { create(:project, :private) } let(:pipeline) { create(:ci_pipeline, project: project) } context 'when rendering status for build' do diff --git a/spec/views/layouts/nav/_project.html.haml_spec.rb b/spec/views/layouts/nav/_project.html.haml_spec.rb index fd1637ca91b..faea2505e40 100644 --- a/spec/views/layouts/nav/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/_project.html.haml_spec.rb @@ -5,7 +5,7 @@ describe 'layouts/nav/_project' do before do stub_container_registry_config(enabled: true) - assign(:project, create(:project)) + assign(:project, create(:project, :repository)) allow(view).to receive(:current_ref).and_return('master') allow(view).to receive(:can?).and_return(true) diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb index f627f9165fb..d9d73f789c5 100644 --- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'notify/pipeline_failed_email.html.haml' do include Devise::Test::ControllerHelpers let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } let(:pipeline) do diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb index ecd096ee579..a793b37e412 100644 --- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'notify/pipeline_success_email.html.haml' do include Devise::Test::ControllerHelpers let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } let(:pipeline) do diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index f8c6cb6b5c6..62af946dcab 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/_home_panel' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project, :public) } let(:notification_settings) do user&.notification_settings_for(project) diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb index af833168bd9..aedbaa66d34 100644 --- a/spec/views/projects/blob/_viewer.html.haml_spec.rb +++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'projects/blob/_viewer.html.haml' do include FakeBlobHelpers - let(:project) { build(:empty_project) } + let(:project) { build(:project) } let(:viewer_class) do Class.new(BlobViewer::Base) do diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb index d2575702ecc..94899e26292 100644 --- a/spec/views/projects/edit.html.haml_spec.rb +++ b/spec/views/projects/edit.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'projects/edit' do include Devise::Test::ControllerHelpers - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:user) { create(:admin) } before do diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb index 37ce7121ccb..aea20d826d0 100644 --- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb +++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'projects/notes/_more_actions_dropdown' do let(:author_user) { create(:user) } let(:not_author_user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let!(:note) { create(:note_on_issue, author: author_user, noteable: issue, project: project) } diff --git a/spec/views/projects/registry/repositories/index.html.haml_spec.rb b/spec/views/projects/registry/repositories/index.html.haml_spec.rb index f13b657d474..cf0aa44a4a2 100644 --- a/spec/views/projects/registry/repositories/index.html.haml_spec.rb +++ b/spec/views/projects/registry/repositories/index.html.haml_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'projects/registry/repositories/index' do let(:group) { create(:group, path: 'group') } - let(:project) { create(:empty_project, group: group, path: 'test') } + let(:project) { create(:project, group: group, path: 'test') } let(:repository) do create(:container_repository, project: project, name: 'image') diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index f65cd9f398f..cb97d17988c 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/tags/index' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do assign(:project, project) diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb index 43334c2c236..b500016016a 100644 --- a/spec/views/shared/projects/_project.html.haml_spec.rb +++ b/spec/views/shared/projects/_project.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'shared/projects/_project.html.haml' do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } it 'should render creator avatar if project has a creator' do render 'shared/projects/project', use_creator_avatar: true, project: project diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index bd5cc651c2b..03b9b99e263 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe AuthorizedProjectsWorker do - let(:project) { create(:empty_project) } + let(:project) { create(:project) } describe '.bulk_perform_and_wait' do it 'schedules the ids and waits for the jobs to complete' do diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index fe70501eeac..e4e77c667b3 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe EmailReceiverWorker do +describe EmailReceiverWorker, :mailer do let(:raw_message) { fixture_file('emails/valid_reply.eml') } context "when reply by email is enabled" do @@ -17,12 +17,16 @@ describe EmailReceiverWorker do context "when an error occurs" do before do - allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError) + allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(error) end - it "sends out a rejection email" do - perform_enqueued_jobs do - described_class.new.perform(raw_message) + context 'when the error is Gitlab::Email::EmptyEmailError' do + let(:error) { Gitlab::Email::EmptyEmailError } + + it 'sends out a rejection email' do + perform_enqueued_jobs do + described_class.new.perform(raw_message) + end email = ActionMailer::Base.deliveries.last expect(email).not_to be_nil @@ -30,6 +34,18 @@ describe EmailReceiverWorker do expect(email.subject).to include("Rejected") end end + + context 'when the error is Gitlab::Email::AutoGeneratedEmailError' do + let(:error) { Gitlab::Email::AutoGeneratedEmailError } + + it 'does not send out any rejection email' do + perform_enqueued_jobs do + described_class.new.perform(raw_message) + end + + should_not_email_anyone + end + end end end diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 5b6b38e0f76..318aad4bc1e 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' -describe EmailsOnPushWorker do +describe EmailsOnPushWorker, :mailer do include RepoHelpers - include EmailHelpers include EmailSpec::Matchers let(:project) { create(:project, :repository) } @@ -90,7 +89,6 @@ describe EmailsOnPushWorker do context "when there is an SMTP error" do before do - reset_delivered_emails! allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError) allow(subject).to receive_message_chain(:logger, :info) perform @@ -114,8 +112,6 @@ describe EmailsOnPushWorker do allow_any_instance_of(Mail::TestMailer).to receive(:deliver!).and_wrap_original do |original, mail| original.call(Mail.new(mail.encoded)) end - - reset_delivered_emails! end it "sends the mail to each of the recipients" do diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb index e4f78999489..54c9a69d329 100644 --- a/spec/workers/expire_pipeline_cache_worker_spec.rb +++ b/spec/workers/expire_pipeline_cache_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ExpirePipelineCacheWorker do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } subject { described_class.new } diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb index c78efc67076..a170c84ab12 100644 --- a/spec/workers/group_destroy_worker_spec.rb +++ b/spec/workers/group_destroy_worker_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe GroupDestroyWorker do let(:group) { create(:group) } let(:user) { create(:admin) } - let!(:project) { create(:empty_project, namespace: group) } + let!(:project) { create(:project, namespace: group) } subject { described_class.new } diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 303193bab9b..ee51000161a 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -27,4 +27,15 @@ describe MergeWorker do expect(source_project.repository.branch_names).not_to include('markdown') end end + + it 'persists merge_jid' do + merge_request = create(:merge_request, merge_jid: nil) + user = create(:user) + worker = described_class.new + + allow(worker).to receive(:jid) { '999' } + + expect { worker.perform(merge_request.id, user.id, {}) } + .to change { merge_request.reload.merge_jid }.from(nil).to('999') + end end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 8533b7b85e9..f2706254284 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -5,14 +5,14 @@ describe NamespacelessProjectDestroyWorker do before do # Stub after_save callbacks that will fail when Project has no namespace - allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil) + allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil) allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil) end describe '#perform' do context 'project has namespace' do it 'does not do anything' do - project = create(:empty_project) + project = create(:project) subject.perform(project.id) @@ -22,7 +22,7 @@ describe NamespacelessProjectDestroyWorker do context 'project has no namespace' do let!(:project) do - project = build(:empty_project, namespace_id: nil) + project = build(:project, namespace_id: nil) project.save(validate: false) project end @@ -54,7 +54,7 @@ describe NamespacelessProjectDestroyWorker do end context 'project forked from another' do - let!(:parent_project) { create(:empty_project) } + let!(:parent_project) { create(:project) } before do create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb new file mode 100644 index 00000000000..4e15ccc534b --- /dev/null +++ b/spec/workers/new_issue_worker_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe NewIssueWorker do + describe '#perform' do + let(:worker) { described_class.new } + + context 'when an issue not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(99, create(:user).id) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find Issue with ID=99, skipping job') + + worker.perform(99, create(:user).id) + end + end + + context 'when a user not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(create(:issue).id, 99) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find User with ID=99, skipping job') + + worker.perform(create(:issue).id, 99) + end + end + + context 'when everything is ok' do + let(:project) { create(:project, :public) } + let(:mentioned) { create(:user) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") } + + it 'creates a new event record' do + expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1) + end + + it 'creates a notification for the assignee' do + expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id).and_return(double(deliver_later: true)) + + worker.perform(issue.id, user.id) + end + end + end +end diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb new file mode 100644 index 00000000000..9e0cbde45b1 --- /dev/null +++ b/spec/workers/new_merge_request_worker_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe NewMergeRequestWorker do + describe '#perform' do + let(:worker) { described_class.new } + + context 'when a merge request not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(99, create(:user).id) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find MergeRequest with ID=99, skipping job') + + worker.perform(99, create(:user).id) + end + end + + context 'when a user not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(create(:merge_request).id, 99) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find User with ID=99, skipping job') + + worker.perform(create(:merge_request).id, 99) + end + end + + context 'when everything is ok' do + let(:project) { create(:project, :public) } + let(:mentioned) { create(:user) } + let(:user) { create(:user) } + let(:merge_request) do + create(:merge_request, source_project: project, description: "mr for #{mentioned.to_reference}") + end + + it 'creates a new event record' do + expect { worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1) + end + + it 'creates a notification for the assignee' do + expect(Notify).to receive(:new_merge_request_email).with(mentioned.id, merge_request.id).and_return(double(deliver_later: true)) + + worker.perform(merge_request.id, user.id) + end + end + end +end diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index 139032d77bd..eb539ffd893 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe PipelineNotificationWorker do - include EmailHelpers - +describe PipelineNotificationWorker, :mailer do let(:pipeline) { create(:ci_pipeline) } describe '#execute' do diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index 14ed8b7811e..75197039f5a 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -23,7 +23,7 @@ describe PipelineScheduleWorker do context 'when there is a scheduled pipeline within next_run_at' do it 'creates a new pipeline' do - expect{ subject }.to change { project.pipelines.count }.by(1) + expect { subject }.to change { project.pipelines.count }.by(1) expect(Ci::Pipeline.last).to be_schedule end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 74a9f90195c..af6a3c9f6c7 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -78,7 +78,7 @@ describe PostReceive do stub_ci_pipeline_to_return_yaml_file end - it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) } + it { expect { subject }.to change { Ci::Pipeline.count }.by(2) } end context "does not create a Ci::Pipeline" do @@ -86,7 +86,7 @@ describe PostReceive do stub_ci_pipeline_yaml_file(nil) end - it { expect{ subject }.not_to change{ Ci::Pipeline.count } } + it { expect { subject }.not_to change { Ci::Pipeline.count } } end end diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 6ebc94bb544..24f8ca67594 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -33,7 +33,7 @@ describe ProcessCommitWorker do end context 'when commit already exists in upstream project' do - let(:forked) { create(:project, :public) } + let(:forked) { create(:project, :public, :repository) } it 'does not process commit message' do create(:forked_project_link, forked_to_project: forked, forked_from_project: project) diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb index 1c183ce54f4..57f83c1dbe9 100644 --- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb +++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb @@ -6,8 +6,8 @@ describe RemoveUnreferencedLfsObjectsWorker do describe '#perform' do let!(:unreferenced_lfs_object1) { create(:lfs_object, oid: '1') } let!(:unreferenced_lfs_object2) { create(:lfs_object, oid: '2') } - let!(:project1) { create(:empty_project, lfs_enabled: true) } - let!(:project2) { create(:empty_project, lfs_enabled: true) } + let!(:project1) { create(:project, lfs_enabled: true) } + let!(:project2) { create(:project, lfs_enabled: true) } let!(:referenced_lfs_object1) { create(:lfs_object, oid: '3') } let!(:referenced_lfs_object2) { create(:lfs_object, oid: '4') } let!(:lfs_objects_project1_1) do diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index bcd97a4f6ef..850b8cd8f5c 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -4,7 +4,7 @@ describe RepositoryCheck::BatchWorker do subject { described_class.new } it 'prefers projects that have never been checked' do - projects = create_list(:empty_project, 3, created_at: 1.week.ago) + projects = create_list(:project, 3, created_at: 1.week.ago) projects[0].update_column(:last_repository_check_at, 4.months.ago) projects[2].update_column(:last_repository_check_at, 3.months.ago) @@ -12,7 +12,7 @@ describe RepositoryCheck::BatchWorker do end it 'sorts projects by last_repository_check_at' do - projects = create_list(:empty_project, 3, created_at: 1.week.ago) + projects = create_list(:project, 3, created_at: 1.week.ago) projects[0].update_column(:last_repository_check_at, 2.months.ago) projects[1].update_column(:last_repository_check_at, 4.months.ago) projects[2].update_column(:last_repository_check_at, 3.months.ago) @@ -21,7 +21,7 @@ describe RepositoryCheck::BatchWorker do end it 'excludes projects that were checked recently' do - projects = create_list(:empty_project, 3, created_at: 1.week.ago) + projects = create_list(:project, 3, created_at: 1.week.ago) projects[0].update_column(:last_repository_check_at, 2.days.ago) projects[1].update_column(:last_repository_check_at, 2.months.ago) projects[2].update_column(:last_repository_check_at, 3.days.ago) @@ -30,7 +30,7 @@ describe RepositoryCheck::BatchWorker do end it 'does nothing when repository checks are disabled' do - create(:empty_project, created_at: 1.week.ago) + create(:project, created_at: 1.week.ago) current_settings = double('settings', repository_checks_enabled: false) expect(subject).to receive(:current_settings) { current_settings } @@ -38,7 +38,7 @@ describe RepositoryCheck::BatchWorker do end it 'skips projects created less than 24 hours ago' do - project = create(:empty_project) + project = create(:project) project.update_column(:created_at, 23.hours.ago) expect(subject.perform).to eq([]) diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb index 3b1a64c5057..1c49415d46c 100644 --- a/spec/workers/repository_check/clear_worker_spec.rb +++ b/spec/workers/repository_check/clear_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe RepositoryCheck::ClearWorker do it 'clears repository check columns' do - project = create(:empty_project) + project = create(:project) project.update_columns( last_repository_check_failed: true, last_repository_check_at: Time.now diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 6b30dabc80e..ca904e512ac 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryImportWorker do - let(:project) { create(:empty_project, :import_scheduled) } + let(:project) { create(:project, :import_scheduled) } subject { described_class.new } diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb index 466277a5e5e..2f5b685a332 100644 --- a/spec/workers/stuck_import_jobs_worker_spec.rb +++ b/spec/workers/stuck_import_jobs_worker_spec.rb @@ -9,7 +9,7 @@ describe StuckImportJobsWorker do end describe 'long running import' do - let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + let(:project) { create(:project, import_jid: '123', import_status: 'started') } before do allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(['123']) @@ -21,7 +21,7 @@ describe StuckImportJobsWorker do end describe 'running import' do - let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + let(:project) { create(:project, import_jid: '123', import_status: 'started') } before do allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb new file mode 100644 index 00000000000..a5ad78393c9 --- /dev/null +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe StuckMergeJobsWorker do + describe 'perform' do + let(:worker) { described_class.new } + + context 'merge job identified as completed' do + it 'updates merge request to merged when locked but has merge_commit_sha' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456)) + mr_with_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: 'foo-bar-baz') + mr_without_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: nil) + + worker.perform + + expect(mr_with_sha.reload).to be_merged + expect(mr_without_sha.reload).to be_opened + end + + it 'updates merge request to opened when locked but has not been merged' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123)) + merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked) + + worker.perform + + expect(merge_request.reload).to be_opened + end + + it 'logs updated stuck merge job ids' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456)) + + create(:merge_request, :locked, merge_jid: '123') + create(:merge_request, :locked, merge_jid: '456') + + expect(Rails).to receive_message_chain(:logger, :info).with('Updated state of locked merge jobs. JIDs: 123, 456') + + worker.perform + end + end + + context 'merge job not identified as completed' do + it 'does not change merge request state when job is not completed yet' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) + + merge_request = create(:merge_request, :locked, merge_jid: '123') + + expect { worker.perform }.not_to change { merge_request.reload.state }.from('locked') + end + end + end +end diff --git a/vendor/assets/javascripts/cropper.js b/vendor/assets/javascripts/cropper.js deleted file mode 100644 index 805485904a5..00000000000 --- a/vendor/assets/javascripts/cropper.js +++ /dev/null @@ -1,2993 +0,0 @@ -/*! - * Cropper v2.3.0 - * https://github.com/fengyuanchen/cropper - * - * Copyright (c) 2014-2016 Fengyuan Chen and contributors - * Released under the MIT license - * - * Date: 2016-02-22T02:13:13.332Z - */ - -(function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object') { - // Node / CommonJS - factory(require('jquery')); - } else { - // Browser globals. - factory(jQuery); - } -})(function ($) { - - 'use strict'; - - // Globals - var $window = $(window); - var $document = $(document); - var location = window.location; - var navigator = window.navigator; - var ArrayBuffer = window.ArrayBuffer; - var Uint8Array = window.Uint8Array; - var DataView = window.DataView; - var btoa = window.btoa; - - // Constants - var NAMESPACE = 'cropper'; - - // Classes - var CLASS_MODAL = 'cropper-modal'; - var CLASS_HIDE = 'cropper-hide'; - var CLASS_HIDDEN = 'cropper-hidden'; - var CLASS_INVISIBLE = 'cropper-invisible'; - var CLASS_MOVE = 'cropper-move'; - var CLASS_CROP = 'cropper-crop'; - var CLASS_DISABLED = 'cropper-disabled'; - var CLASS_BG = 'cropper-bg'; - - // Events - var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown'; - var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove'; - var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel'; - var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll'; - var EVENT_DBLCLICK = 'dblclick'; - var EVENT_LOAD = 'load.' + NAMESPACE; - var EVENT_ERROR = 'error.' + NAMESPACE; - var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace - var EVENT_BUILD = 'build.' + NAMESPACE; - var EVENT_BUILT = 'built.' + NAMESPACE; - var EVENT_CROP_START = 'cropstart.' + NAMESPACE; - var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE; - var EVENT_CROP_END = 'cropend.' + NAMESPACE; - var EVENT_CROP = 'crop.' + NAMESPACE; - var EVENT_ZOOM = 'zoom.' + NAMESPACE; - - // RegExps - var REGEXP_ACTIONS = /e|w|s|n|se|sw|ne|nw|all|crop|move|zoom/; - var REGEXP_DATA_URL = /^data\:/; - var REGEXP_DATA_URL_HEAD = /^data\:([^\;]+)\;base64,/; - var REGEXP_DATA_URL_JPEG = /^data\:image\/jpeg.*;base64,/; - - // Data keys - var DATA_PREVIEW = 'preview'; - var DATA_ACTION = 'action'; - - // Actions - var ACTION_EAST = 'e'; - var ACTION_WEST = 'w'; - var ACTION_SOUTH = 's'; - var ACTION_NORTH = 'n'; - var ACTION_SOUTH_EAST = 'se'; - var ACTION_SOUTH_WEST = 'sw'; - var ACTION_NORTH_EAST = 'ne'; - var ACTION_NORTH_WEST = 'nw'; - var ACTION_ALL = 'all'; - var ACTION_CROP = 'crop'; - var ACTION_MOVE = 'move'; - var ACTION_ZOOM = 'zoom'; - var ACTION_NONE = 'none'; - - // Supports - var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext); - var IS_SAFARI = navigator && /safari/i.test(navigator.userAgent) && /apple computer/i.test(navigator.vendor); - - // Maths - var num = Number; - var min = Math.min; - var max = Math.max; - var abs = Math.abs; - var sin = Math.sin; - var cos = Math.cos; - var sqrt = Math.sqrt; - var round = Math.round; - var floor = Math.floor; - - // Utilities - var fromCharCode = String.fromCharCode; - - function isNumber(n) { - return typeof n === 'number' && !isNaN(n); - } - - function isUndefined(n) { - return typeof n === 'undefined'; - } - - function toArray(obj, offset) { - var args = []; - - // This is necessary for IE8 - if (isNumber(offset)) { - args.push(offset); - } - - return args.slice.apply(obj, args); - } - - // Custom proxy to avoid jQuery's guid - function proxy(fn, context) { - var args = toArray(arguments, 2); - - return function () { - return fn.apply(context, args.concat(toArray(arguments))); - }; - } - - function isCrossOriginURL(url) { - var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i); - - return parts && ( - parts[1] !== location.protocol || - parts[2] !== location.hostname || - parts[3] !== location.port - ); - } - - function addTimestamp(url) { - var timestamp = 'timestamp=' + (new Date()).getTime(); - - return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp); - } - - function getCrossOrigin(crossOrigin) { - return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : ''; - } - - function getImageSize(image, callback) { - var newImage; - - // Modern browsers (ignore Safari, #120 & #509) - if (image.naturalWidth && !IS_SAFARI) { - return callback(image.naturalWidth, image.naturalHeight); - } - - // IE8: Don't use `new Image()` here (#319) - newImage = document.createElement('img'); - - newImage.onload = function () { - callback(this.width, this.height); - }; - - newImage.src = image.src; - } - - function getTransform(options) { - var transforms = []; - var rotate = options.rotate; - var scaleX = options.scaleX; - var scaleY = options.scaleY; - - if (isNumber(rotate)) { - transforms.push('rotate(' + rotate + 'deg)'); - } - - if (isNumber(scaleX) && isNumber(scaleY)) { - transforms.push('scale(' + scaleX + ',' + scaleY + ')'); - } - - return transforms.length ? transforms.join(' ') : 'none'; - } - - function getRotatedSizes(data, isReversed) { - var deg = abs(data.degree) % 180; - var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180; - var sinArc = sin(arc); - var cosArc = cos(arc); - var width = data.width; - var height = data.height; - var aspectRatio = data.aspectRatio; - var newWidth; - var newHeight; - - if (!isReversed) { - newWidth = width * cosArc + height * sinArc; - newHeight = width * sinArc + height * cosArc; - } else { - newWidth = width / (cosArc + sinArc / aspectRatio); - newHeight = newWidth / aspectRatio; - } - - return { - width: newWidth, - height: newHeight - }; - } - - function getSourceCanvas(image, data) { - var canvas = $('<canvas>')[0]; - var context = canvas.getContext('2d'); - var dstX = 0; - var dstY = 0; - var dstWidth = data.naturalWidth; - var dstHeight = data.naturalHeight; - var rotate = data.rotate; - var scaleX = data.scaleX; - var scaleY = data.scaleY; - var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1); - var rotatable = isNumber(rotate) && rotate !== 0; - var advanced = rotatable || scalable; - var canvasWidth = dstWidth * abs(scaleX || 1); - var canvasHeight = dstHeight * abs(scaleY || 1); - var translateX; - var translateY; - var rotated; - - if (scalable) { - translateX = canvasWidth / 2; - translateY = canvasHeight / 2; - } - - if (rotatable) { - rotated = getRotatedSizes({ - width: canvasWidth, - height: canvasHeight, - degree: rotate - }); - - canvasWidth = rotated.width; - canvasHeight = rotated.height; - translateX = canvasWidth / 2; - translateY = canvasHeight / 2; - } - - canvas.width = canvasWidth; - canvas.height = canvasHeight; - - if (advanced) { - dstX = -dstWidth / 2; - dstY = -dstHeight / 2; - - context.save(); - context.translate(translateX, translateY); - } - - if (rotatable) { - context.rotate(rotate * Math.PI / 180); - } - - // Should call `scale` after rotated - if (scalable) { - context.scale(scaleX, scaleY); - } - - context.drawImage(image, floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight)); - - if (advanced) { - context.restore(); - } - - return canvas; - } - - function getTouchesCenter(touches) { - var length = touches.length; - var pageX = 0; - var pageY = 0; - - if (length) { - $.each(touches, function (i, touch) { - pageX += touch.pageX; - pageY += touch.pageY; - }); - - pageX /= length; - pageY /= length; - } - - return { - pageX: pageX, - pageY: pageY - }; - } - - function getStringFromCharCode(dataView, start, length) { - var str = ''; - var i; - - for (i = start, length += start; i < length; i++) { - str += fromCharCode(dataView.getUint8(i)); - } - - return str; - } - - function getOrientation(arrayBuffer) { - var dataView = new DataView(arrayBuffer); - var length = dataView.byteLength; - var orientation; - var exifIDCode; - var tiffOffset; - var firstIFDOffset; - var littleEndian; - var endianness; - var app1Start; - var ifdStart; - var offset; - var i; - - // Only handle JPEG image (start by 0xFFD8) - if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { - offset = 2; - - while (offset < length) { - if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { - app1Start = offset; - break; - } - - offset++; - } - } - - if (app1Start) { - exifIDCode = app1Start + 4; - tiffOffset = app1Start + 10; - - if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { - endianness = dataView.getUint16(tiffOffset); - littleEndian = endianness === 0x4949; - - if (littleEndian || endianness === 0x4D4D /* bigEndian */) { - if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { - firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); - - if (firstIFDOffset >= 0x00000008) { - ifdStart = tiffOffset + firstIFDOffset; - } - } - } - } - } - - if (ifdStart) { - length = dataView.getUint16(ifdStart, littleEndian); - - for (i = 0; i < length; i++) { - offset = ifdStart + i * 12 + 2; - - if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) { - - // 8 is the offset of the current tag's value - offset += 8; - - // Get the original orientation value - orientation = dataView.getUint16(offset, littleEndian); - - // Override the orientation with its default value for Safari (#120) - if (IS_SAFARI) { - dataView.setUint16(offset, 1, littleEndian); - } - - break; - } - } - } - - return orientation; - } - - function dataURLToArrayBuffer(dataURL) { - var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, ''); - var binary = atob(base64); - var length = binary.length; - var arrayBuffer = new ArrayBuffer(length); - var dataView = new Uint8Array(arrayBuffer); - var i; - - for (i = 0; i < length; i++) { - dataView[i] = binary.charCodeAt(i); - } - - return arrayBuffer; - } - - // Only available for JPEG image - function arrayBufferToDataURL(arrayBuffer) { - var dataView = new Uint8Array(arrayBuffer); - var length = dataView.length; - var base64 = ''; - var i; - - for (i = 0; i < length; i++) { - base64 += fromCharCode(dataView[i]); - } - - return 'data:image/jpeg;base64,' + btoa(base64); - } - - function Cropper(element, options) { - this.$element = $(element); - this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options); - this.isLoaded = false; - this.isBuilt = false; - this.isCompleted = false; - this.isRotated = false; - this.isCropped = false; - this.isDisabled = false; - this.isReplaced = false; - this.isLimited = false; - this.wheeling = false; - this.isImg = false; - this.originalUrl = ''; - this.canvas = null; - this.cropBox = null; - this.init(); - } - - Cropper.prototype = { - constructor: Cropper, - - init: function () { - var $this = this.$element; - var url; - - if ($this.is('img')) { - this.isImg = true; - - // Should use `$.fn.attr` here. e.g.: "img/picture.jpg" - this.originalUrl = url = $this.attr('src'); - - // Stop when it's a blank image - if (!url) { - return; - } - - // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg" - url = $this.prop('src'); - } else if ($this.is('canvas') && SUPPORT_CANVAS) { - url = $this[0].toDataURL(); - } - - this.load(url); - }, - - // A shortcut for triggering custom events - trigger: function (type, data) { - var e = $.Event(type, data); - - this.$element.trigger(e); - - return e; - }, - - load: function (url) { - var options = this.options; - var $this = this.$element; - var read; - var xhr; - - if (!url) { - return; - } - - // Trigger build event first - $this.one(EVENT_BUILD, options.build); - - if (this.trigger(EVENT_BUILD).isDefaultPrevented()) { - return; - } - - this.url = url; - this.image = {}; - - if (!options.checkOrientation || !ArrayBuffer) { - return this.clone(); - } - - read = $.proxy(this.read, this); - - // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari - if (REGEXP_DATA_URL.test(url)) { - return REGEXP_DATA_URL_JPEG.test(url) ? - read(dataURLToArrayBuffer(url)) : - this.clone(); - } - - xhr = new XMLHttpRequest(); - - xhr.onerror = xhr.onabort = $.proxy(function () { - this.clone(); - }, this); - - xhr.onload = function () { - read(this.response); - }; - - xhr.open('get', url); - xhr.responseType = 'arraybuffer'; - xhr.send(); - }, - - read: function (arrayBuffer) { - var options = this.options; - var orientation = getOrientation(arrayBuffer); - var image = this.image; - var rotate; - var scaleX; - var scaleY; - - if (orientation > 1) { - this.url = arrayBufferToDataURL(arrayBuffer); - - switch (orientation) { - - // flip horizontal - case 2: - scaleX = -1; - break; - - // rotate left 180° - case 3: - rotate = -180; - break; - - // flip vertical - case 4: - scaleY = -1; - break; - - // flip vertical + rotate right 90° - case 5: - rotate = 90; - scaleY = -1; - break; - - // rotate right 90° - case 6: - rotate = 90; - break; - - // flip horizontal + rotate right 90° - case 7: - rotate = 90; - scaleX = -1; - break; - - // rotate left 90° - case 8: - rotate = -90; - break; - } - } - - if (options.rotatable) { - image.rotate = rotate; - } - - if (options.scalable) { - image.scaleX = scaleX; - image.scaleY = scaleY; - } - - this.clone(); - }, - - clone: function () { - var options = this.options; - var $this = this.$element; - var url = this.url; - var crossOrigin = ''; - var crossOriginUrl; - var $clone; - - if (options.checkCrossOrigin && isCrossOriginURL(url)) { - crossOrigin = $this.prop('crossOrigin'); - - if (crossOrigin) { - crossOriginUrl = url; - } else { - crossOrigin = 'anonymous'; - - // Bust cache (#148) when there is not a "crossOrigin" property - crossOriginUrl = addTimestamp(url); - } - } - - this.crossOrigin = crossOrigin; - this.crossOriginUrl = crossOriginUrl; - this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">'); - - if (this.isImg) { - if ($this[0].complete) { - this.start(); - } else { - $this.one(EVENT_LOAD, $.proxy(this.start, this)); - } - } else { - $clone. - one(EVENT_LOAD, $.proxy(this.start, this)). - one(EVENT_ERROR, $.proxy(this.stop, this)). - addClass(CLASS_HIDE). - insertAfter($this); - } - }, - - start: function () { - var $image = this.$element; - var $clone = this.$clone; - - if (!this.isImg) { - $clone.off(EVENT_ERROR, this.stop); - $image = $clone; - } - - getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) { - $.extend(this.image, { - naturalWidth: naturalWidth, - naturalHeight: naturalHeight, - aspectRatio: naturalWidth / naturalHeight - }); - - this.isLoaded = true; - this.build(); - }, this)); - }, - - stop: function () { - this.$clone.remove(); - this.$clone = null; - }, - - build: function () { - var options = this.options; - var $this = this.$element; - var $clone = this.$clone; - var $cropper; - var $cropBox; - var $face; - - if (!this.isLoaded) { - return; - } - - // Unbuild first when replace - if (this.isBuilt) { - this.unbuild(); - } - - // Create cropper elements - this.$container = $this.parent(); - this.$cropper = $cropper = $(Cropper.TEMPLATE); - this.$canvas = $cropper.find('.cropper-canvas').append($clone); - this.$dragBox = $cropper.find('.cropper-drag-box'); - this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box'); - this.$viewBox = $cropper.find('.cropper-view-box'); - this.$face = $face = $cropBox.find('.cropper-face'); - - // Hide the original image - $this.addClass(CLASS_HIDDEN).after($cropper); - - // Show the clone image if is hidden - if (!this.isImg) { - $clone.removeClass(CLASS_HIDE); - } - - this.initPreview(); - this.bind(); - - options.aspectRatio = max(0, options.aspectRatio) || NaN; - options.viewMode = max(0, min(3, round(options.viewMode))) || 0; - - if (options.autoCrop) { - this.isCropped = true; - - if (options.modal) { - this.$dragBox.addClass(CLASS_MODAL); - } - } else { - $cropBox.addClass(CLASS_HIDDEN); - } - - if (!options.guides) { - $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN); - } - - if (!options.center) { - $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN); - } - - if (options.cropBoxMovable) { - $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL); - } - - if (!options.highlight) { - $face.addClass(CLASS_INVISIBLE); - } - - if (options.background) { - $cropper.addClass(CLASS_BG); - } - - if (!options.cropBoxResizable) { - $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN); - } - - this.setDragMode(options.dragMode); - this.render(); - this.isBuilt = true; - this.setData(options.data); - $this.one(EVENT_BUILT, options.built); - - // Trigger the built event asynchronously to keep `data('cropper')` is defined - setTimeout($.proxy(function () { - this.trigger(EVENT_BUILT); - this.isCompleted = true; - }, this), 0); - }, - - unbuild: function () { - if (!this.isBuilt) { - return; - } - - this.isBuilt = false; - this.isCompleted = false; - this.initialImage = null; - - // Clear `initialCanvas` is necessary when replace - this.initialCanvas = null; - this.initialCropBox = null; - this.container = null; - this.canvas = null; - - // Clear `cropBox` is necessary when replace - this.cropBox = null; - this.unbind(); - - this.resetPreview(); - this.$preview = null; - - this.$viewBox = null; - this.$cropBox = null; - this.$dragBox = null; - this.$canvas = null; - this.$container = null; - - this.$cropper.remove(); - this.$cropper = null; - }, - - render: function () { - this.initContainer(); - this.initCanvas(); - this.initCropBox(); - - this.renderCanvas(); - - if (this.isCropped) { - this.renderCropBox(); - } - }, - - initContainer: function () { - var options = this.options; - var $this = this.$element; - var $container = this.$container; - var $cropper = this.$cropper; - - $cropper.addClass(CLASS_HIDDEN); - $this.removeClass(CLASS_HIDDEN); - - $cropper.css((this.container = { - width: max($container.width(), num(options.minContainerWidth) || 200), - height: max($container.height(), num(options.minContainerHeight) || 100) - })); - - $this.addClass(CLASS_HIDDEN); - $cropper.removeClass(CLASS_HIDDEN); - }, - - // Canvas (image wrapper) - initCanvas: function () { - var viewMode = this.options.viewMode; - var container = this.container; - var containerWidth = container.width; - var containerHeight = container.height; - var image = this.image; - var imageNaturalWidth = image.naturalWidth; - var imageNaturalHeight = image.naturalHeight; - var is90Degree = abs(image.rotate) === 90; - var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth; - var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight; - var aspectRatio = naturalWidth / naturalHeight; - var canvasWidth = containerWidth; - var canvasHeight = containerHeight; - var canvas; - - if (containerHeight * aspectRatio > containerWidth) { - if (viewMode === 3) { - canvasWidth = containerHeight * aspectRatio; - } else { - canvasHeight = containerWidth / aspectRatio; - } - } else { - if (viewMode === 3) { - canvasHeight = containerWidth / aspectRatio; - } else { - canvasWidth = containerHeight * aspectRatio; - } - } - - canvas = { - naturalWidth: naturalWidth, - naturalHeight: naturalHeight, - aspectRatio: aspectRatio, - width: canvasWidth, - height: canvasHeight - }; - - canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2; - canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2; - - this.canvas = canvas; - this.isLimited = (viewMode === 1 || viewMode === 2); - this.limitCanvas(true, true); - this.initialImage = $.extend({}, image); - this.initialCanvas = $.extend({}, canvas); - }, - - limitCanvas: function (isSizeLimited, isPositionLimited) { - var options = this.options; - var viewMode = options.viewMode; - var container = this.container; - var containerWidth = container.width; - var containerHeight = container.height; - var canvas = this.canvas; - var aspectRatio = canvas.aspectRatio; - var cropBox = this.cropBox; - var isCropped = this.isCropped && cropBox; - var minCanvasWidth; - var minCanvasHeight; - var newCanvasLeft; - var newCanvasTop; - - if (isSizeLimited) { - minCanvasWidth = num(options.minCanvasWidth) || 0; - minCanvasHeight = num(options.minCanvasHeight) || 0; - - if (viewMode) { - if (viewMode > 1) { - minCanvasWidth = max(minCanvasWidth, containerWidth); - minCanvasHeight = max(minCanvasHeight, containerHeight); - - if (viewMode === 3) { - if (minCanvasHeight * aspectRatio > minCanvasWidth) { - minCanvasWidth = minCanvasHeight * aspectRatio; - } else { - minCanvasHeight = minCanvasWidth / aspectRatio; - } - } - } else { - if (minCanvasWidth) { - minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0); - } else if (minCanvasHeight) { - minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0); - } else if (isCropped) { - minCanvasWidth = cropBox.width; - minCanvasHeight = cropBox.height; - - if (minCanvasHeight * aspectRatio > minCanvasWidth) { - minCanvasWidth = minCanvasHeight * aspectRatio; - } else { - minCanvasHeight = minCanvasWidth / aspectRatio; - } - } - } - } - - if (minCanvasWidth && minCanvasHeight) { - if (minCanvasHeight * aspectRatio > minCanvasWidth) { - minCanvasHeight = minCanvasWidth / aspectRatio; - } else { - minCanvasWidth = minCanvasHeight * aspectRatio; - } - } else if (minCanvasWidth) { - minCanvasHeight = minCanvasWidth / aspectRatio; - } else if (minCanvasHeight) { - minCanvasWidth = minCanvasHeight * aspectRatio; - } - - canvas.minWidth = minCanvasWidth; - canvas.minHeight = minCanvasHeight; - canvas.maxWidth = Infinity; - canvas.maxHeight = Infinity; - } - - if (isPositionLimited) { - if (viewMode) { - newCanvasLeft = containerWidth - canvas.width; - newCanvasTop = containerHeight - canvas.height; - - canvas.minLeft = min(0, newCanvasLeft); - canvas.minTop = min(0, newCanvasTop); - canvas.maxLeft = max(0, newCanvasLeft); - canvas.maxTop = max(0, newCanvasTop); - - if (isCropped && this.isLimited) { - canvas.minLeft = min( - cropBox.left, - cropBox.left + cropBox.width - canvas.width - ); - canvas.minTop = min( - cropBox.top, - cropBox.top + cropBox.height - canvas.height - ); - canvas.maxLeft = cropBox.left; - canvas.maxTop = cropBox.top; - - if (viewMode === 2) { - if (canvas.width >= containerWidth) { - canvas.minLeft = min(0, newCanvasLeft); - canvas.maxLeft = max(0, newCanvasLeft); - } - - if (canvas.height >= containerHeight) { - canvas.minTop = min(0, newCanvasTop); - canvas.maxTop = max(0, newCanvasTop); - } - } - } - } else { - canvas.minLeft = -canvas.width; - canvas.minTop = -canvas.height; - canvas.maxLeft = containerWidth; - canvas.maxTop = containerHeight; - } - } - }, - - renderCanvas: function (isChanged) { - var canvas = this.canvas; - var image = this.image; - var rotate = image.rotate; - var naturalWidth = image.naturalWidth; - var naturalHeight = image.naturalHeight; - var aspectRatio; - var rotated; - - if (this.isRotated) { - this.isRotated = false; - - // Computes rotated sizes with image sizes - rotated = getRotatedSizes({ - width: image.width, - height: image.height, - degree: rotate - }); - - aspectRatio = rotated.width / rotated.height; - - if (aspectRatio !== canvas.aspectRatio) { - canvas.left -= (rotated.width - canvas.width) / 2; - canvas.top -= (rotated.height - canvas.height) / 2; - canvas.width = rotated.width; - canvas.height = rotated.height; - canvas.aspectRatio = aspectRatio; - canvas.naturalWidth = naturalWidth; - canvas.naturalHeight = naturalHeight; - - // Computes rotated sizes with natural image sizes - if (rotate % 180) { - rotated = getRotatedSizes({ - width: naturalWidth, - height: naturalHeight, - degree: rotate - }); - - canvas.naturalWidth = rotated.width; - canvas.naturalHeight = rotated.height; - } - - this.limitCanvas(true, false); - } - } - - if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) { - canvas.left = canvas.oldLeft; - } - - if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) { - canvas.top = canvas.oldTop; - } - - canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth); - canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight); - - this.limitCanvas(false, true); - - canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft); - canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop); - - this.$canvas.css({ - width: canvas.width, - height: canvas.height, - left: canvas.left, - top: canvas.top - }); - - this.renderImage(); - - if (this.isCropped && this.isLimited) { - this.limitCropBox(true, true); - } - - if (isChanged) { - this.output(); - } - }, - - renderImage: function (isChanged) { - var canvas = this.canvas; - var image = this.image; - var reversed; - - if (image.rotate) { - reversed = getRotatedSizes({ - width: canvas.width, - height: canvas.height, - degree: image.rotate, - aspectRatio: image.aspectRatio - }, true); - } - - $.extend(image, reversed ? { - width: reversed.width, - height: reversed.height, - left: (canvas.width - reversed.width) / 2, - top: (canvas.height - reversed.height) / 2 - } : { - width: canvas.width, - height: canvas.height, - left: 0, - top: 0 - }); - - this.$clone.css({ - width: image.width, - height: image.height, - marginLeft: image.left, - marginTop: image.top, - transform: getTransform(image) - }); - - if (isChanged) { - this.output(); - } - }, - - initCropBox: function () { - var options = this.options; - var canvas = this.canvas; - var aspectRatio = options.aspectRatio; - var autoCropArea = num(options.autoCropArea) || 0.8; - var cropBox = { - width: canvas.width, - height: canvas.height - }; - - if (aspectRatio) { - if (canvas.height * aspectRatio > canvas.width) { - cropBox.height = cropBox.width / aspectRatio; - } else { - cropBox.width = cropBox.height * aspectRatio; - } - } - - this.cropBox = cropBox; - this.limitCropBox(true, true); - - // Initialize auto crop area - cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth); - cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); - - // The width of auto crop area must large than "minWidth", and the height too. (#164) - cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea); - cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea); - cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2; - cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2; - - this.initialCropBox = $.extend({}, cropBox); - }, - - limitCropBox: function (isSizeLimited, isPositionLimited) { - var options = this.options; - var aspectRatio = options.aspectRatio; - var container = this.container; - var containerWidth = container.width; - var containerHeight = container.height; - var canvas = this.canvas; - var cropBox = this.cropBox; - var isLimited = this.isLimited; - var minCropBoxWidth; - var minCropBoxHeight; - var maxCropBoxWidth; - var maxCropBoxHeight; - - if (isSizeLimited) { - minCropBoxWidth = num(options.minCropBoxWidth) || 0; - minCropBoxHeight = num(options.minCropBoxHeight) || 0; - - // The min/maxCropBoxWidth/Height must be less than containerWidth/Height - minCropBoxWidth = min(minCropBoxWidth, containerWidth); - minCropBoxHeight = min(minCropBoxHeight, containerHeight); - maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth); - maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight); - - if (aspectRatio) { - if (minCropBoxWidth && minCropBoxHeight) { - if (minCropBoxHeight * aspectRatio > minCropBoxWidth) { - minCropBoxHeight = minCropBoxWidth / aspectRatio; - } else { - minCropBoxWidth = minCropBoxHeight * aspectRatio; - } - } else if (minCropBoxWidth) { - minCropBoxHeight = minCropBoxWidth / aspectRatio; - } else if (minCropBoxHeight) { - minCropBoxWidth = minCropBoxHeight * aspectRatio; - } - - if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) { - maxCropBoxHeight = maxCropBoxWidth / aspectRatio; - } else { - maxCropBoxWidth = maxCropBoxHeight * aspectRatio; - } - } - - // The minWidth/Height must be less than maxWidth/Height - cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth); - cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight); - cropBox.maxWidth = maxCropBoxWidth; - cropBox.maxHeight = maxCropBoxHeight; - } - - if (isPositionLimited) { - if (isLimited) { - cropBox.minLeft = max(0, canvas.left); - cropBox.minTop = max(0, canvas.top); - cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width; - cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height; - } else { - cropBox.minLeft = 0; - cropBox.minTop = 0; - cropBox.maxLeft = containerWidth - cropBox.width; - cropBox.maxTop = containerHeight - cropBox.height; - } - } - }, - - renderCropBox: function () { - var options = this.options; - var container = this.container; - var containerWidth = container.width; - var containerHeight = container.height; - var cropBox = this.cropBox; - - if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) { - cropBox.left = cropBox.oldLeft; - } - - if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) { - cropBox.top = cropBox.oldTop; - } - - cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth); - cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); - - this.limitCropBox(false, true); - - cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft); - cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop); - - if (options.movable && options.cropBoxMovable) { - - // Turn to move the canvas when the crop box is equal to the container - this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL); - } - - this.$cropBox.css({ - width: cropBox.width, - height: cropBox.height, - left: cropBox.left, - top: cropBox.top - }); - - if (this.isCropped && this.isLimited) { - this.limitCanvas(true, true); - } - - if (!this.isDisabled) { - this.output(); - } - }, - - output: function () { - this.preview(); - - if (this.isCompleted) { - this.trigger(EVENT_CROP, this.getData()); - } else if (!this.isBuilt) { - - // Only trigger one crop event before complete - this.$element.one(EVENT_BUILT, $.proxy(function () { - this.trigger(EVENT_CROP, this.getData()); - }, this)); - } - }, - - initPreview: function () { - var crossOrigin = getCrossOrigin(this.crossOrigin); - var url = crossOrigin ? this.crossOriginUrl : this.url; - var $clone2; - - this.$preview = $(this.options.preview); - this.$clone2 = $clone2 = $('<img' + crossOrigin + ' src="' + url + '">'); - this.$viewBox.html($clone2); - this.$preview.each(function () { - var $this = $(this); - - // Save the original size for recover - $this.data(DATA_PREVIEW, { - width: $this.width(), - height: $this.height(), - html: $this.html() - }); - - /** - * Override img element styles - * Add `display:block` to avoid margin top issue - * (Occur only when margin-top <= -height) - */ - $this.html( - '<img' + crossOrigin + ' src="' + url + '" style="' + - 'display:block;width:100%;height:auto;' + - 'min-width:0!important;min-height:0!important;' + - 'max-width:none!important;max-height:none!important;' + - 'image-orientation:0deg!important;">' - ); - }); - }, - - resetPreview: function () { - this.$preview.each(function () { - var $this = $(this); - var data = $this.data(DATA_PREVIEW); - - $this.css({ - width: data.width, - height: data.height - }).html(data.html).removeData(DATA_PREVIEW); - }); - }, - - preview: function () { - var image = this.image; - var canvas = this.canvas; - var cropBox = this.cropBox; - var cropBoxWidth = cropBox.width; - var cropBoxHeight = cropBox.height; - var width = image.width; - var height = image.height; - var left = cropBox.left - canvas.left - image.left; - var top = cropBox.top - canvas.top - image.top; - - if (!this.isCropped || this.isDisabled) { - return; - } - - this.$clone2.css({ - width: width, - height: height, - marginLeft: -left, - marginTop: -top, - transform: getTransform(image) - }); - - this.$preview.each(function () { - var $this = $(this); - var data = $this.data(DATA_PREVIEW); - var originalWidth = data.width; - var originalHeight = data.height; - var newWidth = originalWidth; - var newHeight = originalHeight; - var ratio = 1; - - if (cropBoxWidth) { - ratio = originalWidth / cropBoxWidth; - newHeight = cropBoxHeight * ratio; - } - - if (cropBoxHeight && newHeight > originalHeight) { - ratio = originalHeight / cropBoxHeight; - newWidth = cropBoxWidth * ratio; - newHeight = originalHeight; - } - - $this.css({ - width: newWidth, - height: newHeight - }).find('img').css({ - width: width * ratio, - height: height * ratio, - marginLeft: -left * ratio, - marginTop: -top * ratio, - transform: getTransform(image) - }); - }); - }, - - bind: function () { - var options = this.options; - var $this = this.$element; - var $cropper = this.$cropper; - - if ($.isFunction(options.cropstart)) { - $this.on(EVENT_CROP_START, options.cropstart); - } - - if ($.isFunction(options.cropmove)) { - $this.on(EVENT_CROP_MOVE, options.cropmove); - } - - if ($.isFunction(options.cropend)) { - $this.on(EVENT_CROP_END, options.cropend); - } - - if ($.isFunction(options.crop)) { - $this.on(EVENT_CROP, options.crop); - } - - if ($.isFunction(options.zoom)) { - $this.on(EVENT_ZOOM, options.zoom); - } - - $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this)); - - if (options.zoomable && options.zoomOnWheel) { - $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this)); - } - - if (options.toggleDragModeOnDblclick) { - $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this)); - } - - $document. - on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))). - on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this))); - - if (options.responsive) { - $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this))); - } - }, - - unbind: function () { - var options = this.options; - var $this = this.$element; - var $cropper = this.$cropper; - - if ($.isFunction(options.cropstart)) { - $this.off(EVENT_CROP_START, options.cropstart); - } - - if ($.isFunction(options.cropmove)) { - $this.off(EVENT_CROP_MOVE, options.cropmove); - } - - if ($.isFunction(options.cropend)) { - $this.off(EVENT_CROP_END, options.cropend); - } - - if ($.isFunction(options.crop)) { - $this.off(EVENT_CROP, options.crop); - } - - if ($.isFunction(options.zoom)) { - $this.off(EVENT_ZOOM, options.zoom); - } - - $cropper.off(EVENT_MOUSE_DOWN, this.cropStart); - - if (options.zoomable && options.zoomOnWheel) { - $cropper.off(EVENT_WHEEL, this.wheel); - } - - if (options.toggleDragModeOnDblclick) { - $cropper.off(EVENT_DBLCLICK, this.dblclick); - } - - $document. - off(EVENT_MOUSE_MOVE, this._cropMove). - off(EVENT_MOUSE_UP, this._cropEnd); - - if (options.responsive) { - $window.off(EVENT_RESIZE, this._resize); - } - }, - - resize: function () { - var restore = this.options.restore; - var $container = this.$container; - var container = this.container; - var canvasData; - var cropBoxData; - var ratio; - - // Check `container` is necessary for IE8 - if (this.isDisabled || !container) { - return; - } - - ratio = $container.width() / container.width; - - // Resize when width changed or height changed - if (ratio !== 1 || $container.height() !== container.height) { - if (restore) { - canvasData = this.getCanvasData(); - cropBoxData = this.getCropBoxData(); - } - - this.render(); - - if (restore) { - this.setCanvasData($.each(canvasData, function (i, n) { - canvasData[i] = n * ratio; - })); - this.setCropBoxData($.each(cropBoxData, function (i, n) { - cropBoxData[i] = n * ratio; - })); - } - } - }, - - dblclick: function () { - if (this.isDisabled) { - return; - } - - if (this.$dragBox.hasClass(CLASS_CROP)) { - this.setDragMode(ACTION_MOVE); - } else { - this.setDragMode(ACTION_CROP); - } - }, - - wheel: function (event) { - var e = event.originalEvent || event; - var ratio = num(this.options.wheelZoomRatio) || 0.1; - var delta = 1; - - if (this.isDisabled) { - return; - } - - event.preventDefault(); - - // Limit wheel speed to prevent zoom too fast - if (this.wheeling) { - return; - } - - this.wheeling = true; - - setTimeout($.proxy(function () { - this.wheeling = false; - }, this), 50); - - if (e.deltaY) { - delta = e.deltaY > 0 ? 1 : -1; - } else if (e.wheelDelta) { - delta = -e.wheelDelta / 120; - } else if (e.detail) { - delta = e.detail > 0 ? 1 : -1; - } - - this.zoom(-delta * ratio, event); - }, - - cropStart: function (event) { - var options = this.options; - var originalEvent = event.originalEvent; - var touches = originalEvent && originalEvent.touches; - var e = event; - var touchesLength; - var action; - - if (this.isDisabled) { - return; - } - - if (touches) { - touchesLength = touches.length; - - if (touchesLength > 1) { - if (options.zoomable && options.zoomOnTouch && touchesLength === 2) { - e = touches[1]; - this.startX2 = e.pageX; - this.startY2 = e.pageY; - action = ACTION_ZOOM; - } else { - return; - } - } - - e = touches[0]; - } - - action = action || $(e.target).data(DATA_ACTION); - - if (REGEXP_ACTIONS.test(action)) { - if (this.trigger(EVENT_CROP_START, { - originalEvent: originalEvent, - action: action - }).isDefaultPrevented()) { - return; - } - - event.preventDefault(); - - this.action = action; - this.cropping = false; - - // IE8 has `event.pageX/Y`, but not `event.originalEvent.pageX/Y` - // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y` - this.startX = e.pageX || originalEvent && originalEvent.pageX; - this.startY = e.pageY || originalEvent && originalEvent.pageY; - - if (action === ACTION_CROP) { - this.cropping = true; - this.$dragBox.addClass(CLASS_MODAL); - } - } - }, - - cropMove: function (event) { - var options = this.options; - var originalEvent = event.originalEvent; - var touches = originalEvent && originalEvent.touches; - var e = event; - var action = this.action; - var touchesLength; - - if (this.isDisabled) { - return; - } - - if (touches) { - touchesLength = touches.length; - - if (touchesLength > 1) { - if (options.zoomable && options.zoomOnTouch && touchesLength === 2) { - e = touches[1]; - this.endX2 = e.pageX; - this.endY2 = e.pageY; - } else { - return; - } - } - - e = touches[0]; - } - - if (action) { - if (this.trigger(EVENT_CROP_MOVE, { - originalEvent: originalEvent, - action: action - }).isDefaultPrevented()) { - return; - } - - event.preventDefault(); - - this.endX = e.pageX || originalEvent && originalEvent.pageX; - this.endY = e.pageY || originalEvent && originalEvent.pageY; - - this.change(e.shiftKey, action === ACTION_ZOOM ? event : null); - } - }, - - cropEnd: function (event) { - var originalEvent = event.originalEvent; - var action = this.action; - - if (this.isDisabled) { - return; - } - - if (action) { - event.preventDefault(); - - if (this.cropping) { - this.cropping = false; - this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal); - } - - this.action = ''; - - this.trigger(EVENT_CROP_END, { - originalEvent: originalEvent, - action: action - }); - } - }, - - change: function (shiftKey, event) { - var options = this.options; - var aspectRatio = options.aspectRatio; - var action = this.action; - var container = this.container; - var canvas = this.canvas; - var cropBox = this.cropBox; - var width = cropBox.width; - var height = cropBox.height; - var left = cropBox.left; - var top = cropBox.top; - var right = left + width; - var bottom = top + height; - var minLeft = 0; - var minTop = 0; - var maxWidth = container.width; - var maxHeight = container.height; - var renderable = true; - var offset; - var range; - - // Locking aspect ratio in "free mode" by holding shift key (#259) - if (!aspectRatio && shiftKey) { - aspectRatio = width && height ? width / height : 1; - } - - if (this.limited) { - minLeft = cropBox.minLeft; - minTop = cropBox.minTop; - maxWidth = minLeft + min(container.width, canvas.left + canvas.width); - maxHeight = minTop + min(container.height, canvas.top + canvas.height); - } - - range = { - x: this.endX - this.startX, - y: this.endY - this.startY - }; - - if (aspectRatio) { - range.X = range.y * aspectRatio; - range.Y = range.x / aspectRatio; - } - - switch (action) { - // Move crop box - case ACTION_ALL: - left += range.x; - top += range.y; - break; - - // Resize crop box - case ACTION_EAST: - if (range.x >= 0 && (right >= maxWidth || aspectRatio && - (top <= minTop || bottom >= maxHeight))) { - - renderable = false; - break; - } - - width += range.x; - - if (aspectRatio) { - height = width / aspectRatio; - top -= range.Y / 2; - } - - if (width < 0) { - action = ACTION_WEST; - width = 0; - } - - break; - - case ACTION_NORTH: - if (range.y <= 0 && (top <= minTop || aspectRatio && - (left <= minLeft || right >= maxWidth))) { - - renderable = false; - break; - } - - height -= range.y; - top += range.y; - - if (aspectRatio) { - width = height * aspectRatio; - left += range.X / 2; - } - - if (height < 0) { - action = ACTION_SOUTH; - height = 0; - } - - break; - - case ACTION_WEST: - if (range.x <= 0 && (left <= minLeft || aspectRatio && - (top <= minTop || bottom >= maxHeight))) { - - renderable = false; - break; - } - - width -= range.x; - left += range.x; - - if (aspectRatio) { - height = width / aspectRatio; - top += range.Y / 2; - } - - if (width < 0) { - action = ACTION_EAST; - width = 0; - } - - break; - - case ACTION_SOUTH: - if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && - (left <= minLeft || right >= maxWidth))) { - - renderable = false; - break; - } - - height += range.y; - - if (aspectRatio) { - width = height * aspectRatio; - left -= range.X / 2; - } - - if (height < 0) { - action = ACTION_NORTH; - height = 0; - } - - break; - - case ACTION_NORTH_EAST: - if (aspectRatio) { - if (range.y <= 0 && (top <= minTop || right >= maxWidth)) { - renderable = false; - break; - } - - height -= range.y; - top += range.y; - width = height * aspectRatio; - } else { - if (range.x >= 0) { - if (right < maxWidth) { - width += range.x; - } else if (range.y <= 0 && top <= minTop) { - renderable = false; - } - } else { - width += range.x; - } - - if (range.y <= 0) { - if (top > minTop) { - height -= range.y; - top += range.y; - } - } else { - height -= range.y; - top += range.y; - } - } - - if (width < 0 && height < 0) { - action = ACTION_SOUTH_WEST; - height = 0; - width = 0; - } else if (width < 0) { - action = ACTION_NORTH_WEST; - width = 0; - } else if (height < 0) { - action = ACTION_SOUTH_EAST; - height = 0; - } - - break; - - case ACTION_NORTH_WEST: - if (aspectRatio) { - if (range.y <= 0 && (top <= minTop || left <= minLeft)) { - renderable = false; - break; - } - - height -= range.y; - top += range.y; - width = height * aspectRatio; - left += range.X; - } else { - if (range.x <= 0) { - if (left > minLeft) { - width -= range.x; - left += range.x; - } else if (range.y <= 0 && top <= minTop) { - renderable = false; - } - } else { - width -= range.x; - left += range.x; - } - - if (range.y <= 0) { - if (top > minTop) { - height -= range.y; - top += range.y; - } - } else { - height -= range.y; - top += range.y; - } - } - - if (width < 0 && height < 0) { - action = ACTION_SOUTH_EAST; - height = 0; - width = 0; - } else if (width < 0) { - action = ACTION_NORTH_EAST; - width = 0; - } else if (height < 0) { - action = ACTION_SOUTH_WEST; - height = 0; - } - - break; - - case ACTION_SOUTH_WEST: - if (aspectRatio) { - if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) { - renderable = false; - break; - } - - width -= range.x; - left += range.x; - height = width / aspectRatio; - } else { - if (range.x <= 0) { - if (left > minLeft) { - width -= range.x; - left += range.x; - } else if (range.y >= 0 && bottom >= maxHeight) { - renderable = false; - } - } else { - width -= range.x; - left += range.x; - } - - if (range.y >= 0) { - if (bottom < maxHeight) { - height += range.y; - } - } else { - height += range.y; - } - } - - if (width < 0 && height < 0) { - action = ACTION_NORTH_EAST; - height = 0; - width = 0; - } else if (width < 0) { - action = ACTION_SOUTH_EAST; - width = 0; - } else if (height < 0) { - action = ACTION_NORTH_WEST; - height = 0; - } - - break; - - case ACTION_SOUTH_EAST: - if (aspectRatio) { - if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) { - renderable = false; - break; - } - - width += range.x; - height = width / aspectRatio; - } else { - if (range.x >= 0) { - if (right < maxWidth) { - width += range.x; - } else if (range.y >= 0 && bottom >= maxHeight) { - renderable = false; - } - } else { - width += range.x; - } - - if (range.y >= 0) { - if (bottom < maxHeight) { - height += range.y; - } - } else { - height += range.y; - } - } - - if (width < 0 && height < 0) { - action = ACTION_NORTH_WEST; - height = 0; - width = 0; - } else if (width < 0) { - action = ACTION_SOUTH_WEST; - width = 0; - } else if (height < 0) { - action = ACTION_NORTH_EAST; - height = 0; - } - - break; - - // Move canvas - case ACTION_MOVE: - this.move(range.x, range.y); - renderable = false; - break; - - // Zoom canvas - case ACTION_ZOOM: - this.zoom((function (x1, y1, x2, y2) { - var z1 = sqrt(x1 * x1 + y1 * y1); - var z2 = sqrt(x2 * x2 + y2 * y2); - - return (z2 - z1) / z1; - })( - abs(this.startX - this.startX2), - abs(this.startY - this.startY2), - abs(this.endX - this.endX2), - abs(this.endY - this.endY2) - ), event); - this.startX2 = this.endX2; - this.startY2 = this.endY2; - renderable = false; - break; - - // Create crop box - case ACTION_CROP: - if (!range.x || !range.y) { - renderable = false; - break; - } - - offset = this.$cropper.offset(); - left = this.startX - offset.left; - top = this.startY - offset.top; - width = cropBox.minWidth; - height = cropBox.minHeight; - - if (range.x > 0) { - action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST; - } else if (range.x < 0) { - left -= width; - action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST; - } - - if (range.y < 0) { - top -= height; - } - - // Show the crop box if is hidden - if (!this.isCropped) { - this.$cropBox.removeClass(CLASS_HIDDEN); - this.isCropped = true; - - if (this.limited) { - this.limitCropBox(true, true); - } - } - - break; - - // No default - } - - if (renderable) { - cropBox.width = width; - cropBox.height = height; - cropBox.left = left; - cropBox.top = top; - this.action = action; - - this.renderCropBox(); - } - - // Override - this.startX = this.endX; - this.startY = this.endY; - }, - - // Show the crop box manually - crop: function () { - if (!this.isBuilt || this.isDisabled) { - return; - } - - if (!this.isCropped) { - this.isCropped = true; - this.limitCropBox(true, true); - - if (this.options.modal) { - this.$dragBox.addClass(CLASS_MODAL); - } - - this.$cropBox.removeClass(CLASS_HIDDEN); - } - - this.setCropBoxData(this.initialCropBox); - }, - - // Reset the image and crop box to their initial states - reset: function () { - if (!this.isBuilt || this.isDisabled) { - return; - } - - this.image = $.extend({}, this.initialImage); - this.canvas = $.extend({}, this.initialCanvas); - this.cropBox = $.extend({}, this.initialCropBox); - - this.renderCanvas(); - - if (this.isCropped) { - this.renderCropBox(); - } - }, - - // Clear the crop box - clear: function () { - if (!this.isCropped || this.isDisabled) { - return; - } - - $.extend(this.cropBox, { - left: 0, - top: 0, - width: 0, - height: 0 - }); - - this.isCropped = false; - this.renderCropBox(); - - this.limitCanvas(true, true); - - // Render canvas after crop box rendered - this.renderCanvas(); - - this.$dragBox.removeClass(CLASS_MODAL); - this.$cropBox.addClass(CLASS_HIDDEN); - }, - - /** - * Replace the image's src and rebuild the cropper - * - * @param {String} url - * @param {Boolean} onlyColorChanged (optional) - */ - replace: function (url, onlyColorChanged) { - if (!this.isDisabled && url) { - if (this.isImg) { - this.$element.attr('src', url); - } - - if (onlyColorChanged) { - this.url = url; - this.$clone.attr('src', url); - - if (this.isBuilt) { - this.$preview.find('img').add(this.$clone2).attr('src', url); - } - } else { - if (this.isImg) { - this.isReplaced = true; - } - - // Clear previous data - this.options.data = null; - this.load(url); - } - } - }, - - // Enable (unfreeze) the cropper - enable: function () { - if (this.isBuilt) { - this.isDisabled = false; - this.$cropper.removeClass(CLASS_DISABLED); - } - }, - - // Disable (freeze) the cropper - disable: function () { - if (this.isBuilt) { - this.isDisabled = true; - this.$cropper.addClass(CLASS_DISABLED); - } - }, - - // Destroy the cropper and remove the instance from the image - destroy: function () { - var $this = this.$element; - - if (this.isLoaded) { - if (this.isImg && this.isReplaced) { - $this.attr('src', this.originalUrl); - } - - this.unbuild(); - $this.removeClass(CLASS_HIDDEN); - } else { - if (this.isImg) { - $this.off(EVENT_LOAD, this.start); - } else if (this.$clone) { - this.$clone.remove(); - } - } - - $this.removeData(NAMESPACE); - }, - - /** - * Move the canvas with relative offsets - * - * @param {Number} offsetX - * @param {Number} offsetY (optional) - */ - move: function (offsetX, offsetY) { - var canvas = this.canvas; - - this.moveTo( - isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX), - isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY) - ); - }, - - /** - * Move the canvas to an absolute point - * - * @param {Number} x - * @param {Number} y (optional) - */ - moveTo: function (x, y) { - var canvas = this.canvas; - var isChanged = false; - - // If "y" is not present, its default value is "x" - if (isUndefined(y)) { - y = x; - } - - x = num(x); - y = num(y); - - if (this.isBuilt && !this.isDisabled && this.options.movable) { - if (isNumber(x)) { - canvas.left = x; - isChanged = true; - } - - if (isNumber(y)) { - canvas.top = y; - isChanged = true; - } - - if (isChanged) { - this.renderCanvas(true); - } - } - }, - - /** - * Zoom the canvas with a relative ratio - * - * @param {Number} ratio - * @param {jQuery Event} _event (private) - */ - zoom: function (ratio, _event) { - var canvas = this.canvas; - - ratio = num(ratio); - - if (ratio < 0) { - ratio = 1 / (1 - ratio); - } else { - ratio = 1 + ratio; - } - - this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event); - }, - - /** - * Zoom the canvas to an absolute ratio - * - * @param {Number} ratio - * @param {jQuery Event} _event (private) - */ - zoomTo: function (ratio, _event) { - var options = this.options; - var canvas = this.canvas; - var width = canvas.width; - var height = canvas.height; - var naturalWidth = canvas.naturalWidth; - var naturalHeight = canvas.naturalHeight; - var originalEvent; - var newWidth; - var newHeight; - var offset; - var center; - - ratio = num(ratio); - - if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) { - newWidth = naturalWidth * ratio; - newHeight = naturalHeight * ratio; - - if (_event) { - originalEvent = _event.originalEvent; - } - - if (this.trigger(EVENT_ZOOM, { - originalEvent: originalEvent, - oldRatio: width / naturalWidth, - ratio: newWidth / naturalWidth - }).isDefaultPrevented()) { - return; - } - - if (originalEvent) { - offset = this.$cropper.offset(); - center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : { - pageX: _event.pageX || originalEvent.pageX || 0, - pageY: _event.pageY || originalEvent.pageY || 0 - }; - - // Zoom from the triggering point of the event - canvas.left -= (newWidth - width) * ( - ((center.pageX - offset.left) - canvas.left) / width - ); - canvas.top -= (newHeight - height) * ( - ((center.pageY - offset.top) - canvas.top) / height - ); - } else { - - // Zoom from the center of the canvas - canvas.left -= (newWidth - width) / 2; - canvas.top -= (newHeight - height) / 2; - } - - canvas.width = newWidth; - canvas.height = newHeight; - this.renderCanvas(true); - } - }, - - /** - * Rotate the canvas with a relative degree - * - * @param {Number} degree - */ - rotate: function (degree) { - this.rotateTo((this.image.rotate || 0) + num(degree)); - }, - - /** - * Rotate the canvas to an absolute degree - * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate() - * - * @param {Number} degree - */ - rotateTo: function (degree) { - degree = num(degree); - - if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) { - this.image.rotate = degree % 360; - this.isRotated = true; - this.renderCanvas(true); - } - }, - - /** - * Scale the image - * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale() - * - * @param {Number} scaleX - * @param {Number} scaleY (optional) - */ - scale: function (scaleX, scaleY) { - var image = this.image; - var isChanged = false; - - // If "scaleY" is not present, its default value is "scaleX" - if (isUndefined(scaleY)) { - scaleY = scaleX; - } - - scaleX = num(scaleX); - scaleY = num(scaleY); - - if (this.isBuilt && !this.isDisabled && this.options.scalable) { - if (isNumber(scaleX)) { - image.scaleX = scaleX; - isChanged = true; - } - - if (isNumber(scaleY)) { - image.scaleY = scaleY; - isChanged = true; - } - - if (isChanged) { - this.renderImage(true); - } - } - }, - - /** - * Scale the abscissa of the image - * - * @param {Number} scaleX - */ - scaleX: function (scaleX) { - var scaleY = this.image.scaleY; - - this.scale(scaleX, isNumber(scaleY) ? scaleY : 1); - }, - - /** - * Scale the ordinate of the image - * - * @param {Number} scaleY - */ - scaleY: function (scaleY) { - var scaleX = this.image.scaleX; - - this.scale(isNumber(scaleX) ? scaleX : 1, scaleY); - }, - - /** - * Get the cropped area position and size data (base on the original image) - * - * @param {Boolean} isRounded (optional) - * @return {Object} data - */ - getData: function (isRounded) { - var options = this.options; - var image = this.image; - var canvas = this.canvas; - var cropBox = this.cropBox; - var ratio; - var data; - - if (this.isBuilt && this.isCropped) { - data = { - x: cropBox.left - canvas.left, - y: cropBox.top - canvas.top, - width: cropBox.width, - height: cropBox.height - }; - - ratio = image.width / image.naturalWidth; - - $.each(data, function (i, n) { - n = n / ratio; - data[i] = isRounded ? round(n) : n; - }); - - } else { - data = { - x: 0, - y: 0, - width: 0, - height: 0 - }; - } - - if (options.rotatable) { - data.rotate = image.rotate || 0; - } - - if (options.scalable) { - data.scaleX = image.scaleX || 1; - data.scaleY = image.scaleY || 1; - } - - return data; - }, - - /** - * Set the cropped area position and size with new data - * - * @param {Object} data - */ - setData: function (data) { - var options = this.options; - var image = this.image; - var canvas = this.canvas; - var cropBoxData = {}; - var isRotated; - var isScaled; - var ratio; - - if ($.isFunction(data)) { - data = data.call(this.element); - } - - if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) { - if (options.rotatable) { - if (isNumber(data.rotate) && data.rotate !== image.rotate) { - image.rotate = data.rotate; - this.isRotated = isRotated = true; - } - } - - if (options.scalable) { - if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) { - image.scaleX = data.scaleX; - isScaled = true; - } - - if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) { - image.scaleY = data.scaleY; - isScaled = true; - } - } - - if (isRotated) { - this.renderCanvas(); - } else if (isScaled) { - this.renderImage(); - } - - ratio = image.width / image.naturalWidth; - - if (isNumber(data.x)) { - cropBoxData.left = data.x * ratio + canvas.left; - } - - if (isNumber(data.y)) { - cropBoxData.top = data.y * ratio + canvas.top; - } - - if (isNumber(data.width)) { - cropBoxData.width = data.width * ratio; - } - - if (isNumber(data.height)) { - cropBoxData.height = data.height * ratio; - } - - this.setCropBoxData(cropBoxData); - } - }, - - /** - * Get the container size data - * - * @return {Object} data - */ - getContainerData: function () { - return this.isBuilt ? this.container : {}; - }, - - /** - * Get the image position and size data - * - * @return {Object} data - */ - getImageData: function () { - return this.isLoaded ? this.image : {}; - }, - - /** - * Get the canvas position and size data - * - * @return {Object} data - */ - getCanvasData: function () { - var canvas = this.canvas; - var data = {}; - - if (this.isBuilt) { - $.each([ - 'left', - 'top', - 'width', - 'height', - 'naturalWidth', - 'naturalHeight' - ], function (i, n) { - data[n] = canvas[n]; - }); - } - - return data; - }, - - /** - * Set the canvas position and size with new data - * - * @param {Object} data - */ - setCanvasData: function (data) { - var canvas = this.canvas; - var aspectRatio = canvas.aspectRatio; - - if ($.isFunction(data)) { - data = data.call(this.$element); - } - - if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) { - if (isNumber(data.left)) { - canvas.left = data.left; - } - - if (isNumber(data.top)) { - canvas.top = data.top; - } - - if (isNumber(data.width)) { - canvas.width = data.width; - canvas.height = data.width / aspectRatio; - } else if (isNumber(data.height)) { - canvas.height = data.height; - canvas.width = data.height * aspectRatio; - } - - this.renderCanvas(true); - } - }, - - /** - * Get the crop box position and size data - * - * @return {Object} data - */ - getCropBoxData: function () { - var cropBox = this.cropBox; - var data; - - if (this.isBuilt && this.isCropped) { - data = { - left: cropBox.left, - top: cropBox.top, - width: cropBox.width, - height: cropBox.height - }; - } - - return data || {}; - }, - - /** - * Set the crop box position and size with new data - * - * @param {Object} data - */ - setCropBoxData: function (data) { - var cropBox = this.cropBox; - var aspectRatio = this.options.aspectRatio; - var isWidthChanged; - var isHeightChanged; - - if ($.isFunction(data)) { - data = data.call(this.$element); - } - - if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) { - - if (isNumber(data.left)) { - cropBox.left = data.left; - } - - if (isNumber(data.top)) { - cropBox.top = data.top; - } - - if (isNumber(data.width)) { - isWidthChanged = true; - cropBox.width = data.width; - } - - if (isNumber(data.height)) { - isHeightChanged = true; - cropBox.height = data.height; - } - - if (aspectRatio) { - if (isWidthChanged) { - cropBox.height = cropBox.width / aspectRatio; - } else if (isHeightChanged) { - cropBox.width = cropBox.height * aspectRatio; - } - } - - this.renderCropBox(); - } - }, - - /** - * Get a canvas drawn the cropped image - * - * @param {Object} options (optional) - * @return {HTMLCanvasElement} canvas - */ - getCroppedCanvas: function (options) { - var originalWidth; - var originalHeight; - var canvasWidth; - var canvasHeight; - var scaledWidth; - var scaledHeight; - var scaledRatio; - var aspectRatio; - var canvas; - var context; - var data; - - if (!this.isBuilt || !this.isCropped || !SUPPORT_CANVAS) { - return; - } - - if (!$.isPlainObject(options)) { - options = {}; - } - - data = this.getData(); - originalWidth = data.width; - originalHeight = data.height; - aspectRatio = originalWidth / originalHeight; - - if ($.isPlainObject(options)) { - scaledWidth = options.width; - scaledHeight = options.height; - - if (scaledWidth) { - scaledHeight = scaledWidth / aspectRatio; - scaledRatio = scaledWidth / originalWidth; - } else if (scaledHeight) { - scaledWidth = scaledHeight * aspectRatio; - scaledRatio = scaledHeight / originalHeight; - } - } - - // The canvas element will use `Math.floor` on a float number, so floor first - canvasWidth = floor(scaledWidth || originalWidth); - canvasHeight = floor(scaledHeight || originalHeight); - - canvas = $('<canvas>')[0]; - canvas.width = canvasWidth; - canvas.height = canvasHeight; - context = canvas.getContext('2d'); - - if (options.fillColor) { - context.fillStyle = options.fillColor; - context.fillRect(0, 0, canvasWidth, canvasHeight); - } - - // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage - context.drawImage.apply(context, (function () { - var source = getSourceCanvas(this.$clone[0], this.image); - var sourceWidth = source.width; - var sourceHeight = source.height; - var canvas = this.canvas; - var params = [source]; - - // Source canvas - var srcX = data.x + canvas.naturalWidth * (abs(data.scaleX || 1) - 1) / 2; - var srcY = data.y + canvas.naturalHeight * (abs(data.scaleY || 1) - 1) / 2; - var srcWidth; - var srcHeight; - - // Destination canvas - var dstX; - var dstY; - var dstWidth; - var dstHeight; - - if (srcX <= -originalWidth || srcX > sourceWidth) { - srcX = srcWidth = dstX = dstWidth = 0; - } else if (srcX <= 0) { - dstX = -srcX; - srcX = 0; - srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX); - } else if (srcX <= sourceWidth) { - dstX = 0; - srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX); - } - - if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) { - srcY = srcHeight = dstY = dstHeight = 0; - } else if (srcY <= 0) { - dstY = -srcY; - srcY = 0; - srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY); - } else if (srcY <= sourceHeight) { - dstY = 0; - srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY); - } - - // All the numerical parameters should be integer for `drawImage` (#476) - params.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight)); - - // Scale destination sizes - if (scaledRatio) { - dstX *= scaledRatio; - dstY *= scaledRatio; - dstWidth *= scaledRatio; - dstHeight *= scaledRatio; - } - - // Avoid "IndexSizeError" in IE and Firefox - if (dstWidth > 0 && dstHeight > 0) { - params.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight)); - } - - return params; - }).call(this)); - - return canvas; - }, - - /** - * Change the aspect ratio of the crop box - * - * @param {Number} aspectRatio - */ - setAspectRatio: function (aspectRatio) { - var options = this.options; - - if (!this.isDisabled && !isUndefined(aspectRatio)) { - - // 0 -> NaN - options.aspectRatio = max(0, aspectRatio) || NaN; - - if (this.isBuilt) { - this.initCropBox(); - - if (this.isCropped) { - this.renderCropBox(); - } - } - } - }, - - /** - * Change the drag mode - * - * @param {String} mode (optional) - */ - setDragMode: function (mode) { - var options = this.options; - var croppable; - var movable; - - if (this.isLoaded && !this.isDisabled) { - croppable = mode === ACTION_CROP; - movable = options.movable && mode === ACTION_MOVE; - mode = (croppable || movable) ? mode : ACTION_NONE; - - this.$dragBox. - data(DATA_ACTION, mode). - toggleClass(CLASS_CROP, croppable). - toggleClass(CLASS_MOVE, movable); - - if (!options.cropBoxMovable) { - - // Sync drag mode to crop box when it is not movable(#300) - this.$face. - data(DATA_ACTION, mode). - toggleClass(CLASS_CROP, croppable). - toggleClass(CLASS_MOVE, movable); - } - } - } - }; - - Cropper.DEFAULTS = { - - // Define the view mode of the cropper - viewMode: 0, // 0, 1, 2, 3 - - // Define the dragging mode of the cropper - dragMode: 'crop', // 'crop', 'move' or 'none' - - // Define the aspect ratio of the crop box - aspectRatio: NaN, - - // An object with the previous cropping result data - data: null, - - // A jQuery selector for adding extra containers to preview - preview: '', - - // Re-render the cropper when resize the window - responsive: true, - - // Restore the cropped area after resize the window - restore: true, - - // Check if the current image is a cross-origin image - checkCrossOrigin: true, - - // Check the current image's Exif Orientation information - checkOrientation: true, - - // Show the black modal - modal: true, - - // Show the dashed lines for guiding - guides: true, - - // Show the center indicator for guiding - center: true, - - // Show the white modal to highlight the crop box - highlight: true, - - // Show the grid background - background: true, - - // Enable to crop the image automatically when initialize - autoCrop: true, - - // Define the percentage of automatic cropping area when initializes - autoCropArea: 0.8, - - // Enable to move the image - movable: true, - - // Enable to rotate the image - rotatable: true, - - // Enable to scale the image - scalable: true, - - // Enable to zoom the image - zoomable: true, - - // Enable to zoom the image by dragging touch - zoomOnTouch: true, - - // Enable to zoom the image by wheeling mouse - zoomOnWheel: true, - - // Define zoom ratio when zoom the image by wheeling mouse - wheelZoomRatio: 0.1, - - // Enable to move the crop box - cropBoxMovable: true, - - // Enable to resize the crop box - cropBoxResizable: true, - - // Toggle drag mode between "crop" and "move" when click twice on the cropper - toggleDragModeOnDblclick: true, - - // Size limitation - minCanvasWidth: 0, - minCanvasHeight: 0, - minCropBoxWidth: 0, - minCropBoxHeight: 0, - minContainerWidth: 200, - minContainerHeight: 100, - - // Shortcuts of events - build: null, - built: null, - cropstart: null, - cropmove: null, - cropend: null, - crop: null, - zoom: null - }; - - Cropper.setDefaults = function (options) { - $.extend(Cropper.DEFAULTS, options); - }; - - Cropper.TEMPLATE = ( - '<div class="cropper-container">' + - '<div class="cropper-wrap-box">' + - '<div class="cropper-canvas"></div>' + - '</div>' + - '<div class="cropper-drag-box"></div>' + - '<div class="cropper-crop-box">' + - '<span class="cropper-view-box"></span>' + - '<span class="cropper-dashed dashed-h"></span>' + - '<span class="cropper-dashed dashed-v"></span>' + - '<span class="cropper-center"></span>' + - '<span class="cropper-face"></span>' + - '<span class="cropper-line line-e" data-action="e"></span>' + - '<span class="cropper-line line-n" data-action="n"></span>' + - '<span class="cropper-line line-w" data-action="w"></span>' + - '<span class="cropper-line line-s" data-action="s"></span>' + - '<span class="cropper-point point-e" data-action="e"></span>' + - '<span class="cropper-point point-n" data-action="n"></span>' + - '<span class="cropper-point point-w" data-action="w"></span>' + - '<span class="cropper-point point-s" data-action="s"></span>' + - '<span class="cropper-point point-ne" data-action="ne"></span>' + - '<span class="cropper-point point-nw" data-action="nw"></span>' + - '<span class="cropper-point point-sw" data-action="sw"></span>' + - '<span class="cropper-point point-se" data-action="se"></span>' + - '</div>' + - '</div>' - ); - - // Save the other cropper - Cropper.other = $.fn.cropper; - - // Register as jQuery plugin - $.fn.cropper = function (option) { - var args = toArray(arguments, 1); - var result; - - this.each(function () { - var $this = $(this); - var data = $this.data(NAMESPACE); - var options; - var fn; - - if (!data) { - if (/destroy/.test(option)) { - return; - } - - options = $.extend({}, $this.data(), $.isPlainObject(option) && option); - $this.data(NAMESPACE, (data = new Cropper(this, options))); - } - - if (typeof option === 'string' && $.isFunction(fn = data[option])) { - result = fn.apply(data, args); - } - }); - - return isUndefined(result) ? this : result; - }; - - $.fn.cropper.Constructor = Cropper; - $.fn.cropper.setDefaults = Cropper.setDefaults; - - // No conflict - $.fn.cropper.noConflict = function () { - $.fn.cropper = Cropper.other; - return this; - }; - -}); diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js deleted file mode 100644 index 7653f25df4b..00000000000 --- a/vendor/assets/javascripts/jquery.nicescroll.js +++ /dev/null @@ -1,3634 +0,0 @@ -/* jquery.nicescroll --- version 3.6.0 --- copyright 2014-11-21 InuYaksa*2014 --- licensed under the MIT --- --- http://nicescroll.areaaperta.com/ --- https://github.com/inuyaksa/jquery.nicescroll --- -*/ - -(function(factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as anonymous module. - define(['jquery'], factory); - } else { - // Browser globals. - factory(jQuery); - } -}(function(jQuery) { - "use strict"; - - // globals - var domfocus = false; - var mousefocus = false; - var tabindexcounter = 0; - var ascrailcounter = 2000; - var globalmaxzindex = 0; - - var $ = jQuery; // sandbox - - // http://stackoverflow.com/questions/2161159/get-script-path - function getScriptPath() { - var scripts = document.getElementsByTagName('script'); - var path = scripts[scripts.length - 1].src.split('?')[0]; - return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : ''; - } - - var vendors = ['webkit','ms','moz','o']; - - var setAnimationFrame = window.requestAnimationFrame || false; - var clearAnimationFrame = window.cancelAnimationFrame || false; - - if (!setAnimationFrame) { // legacy detection - for (var vx in vendors) { - var v = vendors[vx]; - if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame']; - if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame']; - } - } - - var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false; - - var _globaloptions = { - zindex: "auto", - cursoropacitymin: 0, - cursoropacitymax: 1, - cursorcolor: "#424242", - cursorwidth: "5px", - cursorborder: "1px solid #fff", - cursorborderradius: "5px", - scrollspeed: 60, - mousescrollstep: 8 * 3, - touchbehavior: false, - hwacceleration: true, - usetransition: true, - boxzoom: false, - dblclickzoom: true, - gesturezoom: true, - grabcursorenabled: true, - autohidemode: true, - background: "", - iframeautoresize: true, - cursorminheight: 32, - preservenativescrolling: true, - railoffset: false, - railhoffset: false, - bouncescroll: true, - spacebarenabled: true, - railpadding: { - top: 0, - right: 0, - left: 0, - bottom: 0 - }, - disableoutline: true, - horizrailenabled: true, - railalign: "right", - railvalign: "bottom", - enabletranslate3d: true, - enablemousewheel: true, - enablekeyboard: true, - smoothscroll: true, - sensitiverail: true, - enablemouselockapi: true, - // cursormaxheight:false, - cursorfixedheight: false, - directionlockdeadzone: 6, - hidecursordelay: 400, - nativeparentscrolling: true, - enablescrollonselection: true, - overflowx: true, - overflowy: true, - cursordragspeed: 0.3, - rtlmode: "auto", - cursordragontouch: false, - oneaxismousemode: "auto", - scriptpath: getScriptPath(), - preventmultitouchscrolling: true - }; - - var browserdetected = false; - - var getBrowserDetection = function() { - - if (browserdetected) return browserdetected; - - var _el = document.createElement('DIV'), - _style = _el.style, - _agent = navigator.userAgent, - _platform = navigator.platform, - d = {}; - - d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document; - - d.isopera = ("opera" in window); // 12- - d.isopera12 = (d.isopera && ("getUserMedia" in navigator)); - d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]"); - - d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10- - d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older - d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7)); - d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8); - d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9); - d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10); - d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+ - - d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango - if (d.isie9mobile) d.isie9 = false; - d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0 - - d.ismozilla = ("MozAppearance" in _style); - - d.iswebkit = ("WebkitAppearance" in _style); - - d.ischrome = ("chrome" in window); - d.ischrome22 = (d.ischrome && d.haspointerlock); - d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix) - - d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation - d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events - d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec - - d.ismac = /^mac$/i.test(_platform); - - d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform)); - d.isios4 = ((d.isios) && !("seal" in Object)); - d.isios7 = ((d.isios)&&("webkitHidden" in document)); //iOS 7+ - - d.isandroid = (/android/i.test(_agent)); - - d.haseventlistener = ("addEventListener" in _el); - - d.trstyle = false; - d.hastransform = false; - d.hastranslate3d = false; - d.transitionstyle = false; - d.hastransition = false; - d.transitionend = false; - - var a; - var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform']; - for (a = 0; a < check.length; a++) { - if (typeof _style[check[a]] != "undefined") { - d.trstyle = check[a]; - break; - } - } - d.hastransform = (!!d.trstyle); - if (d.hastransform) { - _style[d.trstyle] = "translate3d(1px,2px,3px)"; - d.hastranslate3d = /translate3d/.test(_style[d.trstyle]); - } - - d.transitionstyle = false; - d.prefixstyle = ''; - d.transitionend = false; - check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition']; - var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-']; - var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd']; - for (a = 0; a < check.length; a++) { - if (check[a] in _style) { - d.transitionstyle = check[a]; - d.prefixstyle = prefix[a]; - d.transitionend = evs[a]; - break; - } - } - if (d.ischrome26) { // always use prefix - d.prefixstyle = prefix[1]; - } - - d.hastransition = (d.transitionstyle); - - function detectCursorGrab() { - var lst = ['-webkit-grab', '-moz-grab', 'grab']; - if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug - for (var a = 0; a < lst.length; a++) { - var p = lst[a]; - _style.cursor = p; - if (_style.cursor == p) return p; - } - return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor! - } - d.cursorgrabvalue = detectCursorGrab(); - - d.hasmousecapture = ("setCapture" in _el); - - d.hasMutationObserver = (ClsMutationObserver !== false); - - _el = null; //memory released - - browserdetected = d; - - return d; - }; - - var NiceScrollClass = function(myopt, me) { - - var self = this; - - this.version = '3.6.0'; - this.name = 'nicescroll'; - - this.me = me; - - this.opt = { - doc: $("body"), - win: false - }; - - $.extend(this.opt, _globaloptions); // clone opts - - // Options for internal use - this.opt.snapbackspeed = 80; - - if (myopt || false) { - for (var a in self.opt) { - if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a]; - } - } - - this.doc = self.opt.doc; - this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : ''; - this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName); - this.haswrapper = (self.opt.win !== false); - this.win = self.opt.win || (this.ispage ? $(window) : this.doc); - this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win; - this.body = $("body"); - this.viewport = false; - - this.isfixed = false; - - this.iframe = false; - this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME')); - - this.istextarea = (this.win[0].nodeName == 'TEXTAREA'); - - this.forcescreen = false; //force to use screen position on events - - this.canshowonmouseevent = (self.opt.autohidemode != "scroll"); - - // Events jump table - this.onmousedown = false; - this.onmouseup = false; - this.onmousemove = false; - this.onmousewheel = false; - this.onkeypress = false; - this.ongesturezoom = false; - this.onclick = false; - - // Nicescroll custom events - this.onscrollstart = false; - this.onscrollend = false; - this.onscrollcancel = false; - - this.onzoomin = false; - this.onzoomout = false; - - // Let's start! - this.view = false; - this.page = false; - - this.scroll = { - x: 0, - y: 0 - }; - this.scrollratio = { - x: 0, - y: 0 - }; - this.cursorheight = 20; - this.scrollvaluemax = 0; - - this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true); - // this.checkrtlmode = false; - - this.scrollrunning = false; - - this.scrollmom = false; - - this.observer = false; // observer div changes - this.observerremover = false; // observer on parent for remove detection - this.observerbody = false; // observer on body for position change - - do { - this.id = "ascrail" + (ascrailcounter++); - } while (document.getElementById(this.id)); - - this.rail = false; - this.cursor = false; - this.cursorfreezed = false; - this.selectiondrag = false; - - this.zoom = false; - this.zoomactive = false; - - this.hasfocus = false; - this.hasmousefocus = false; - - this.visibility = true; - this.railslocked = false; // locked by resize - this.locked = false; // prevent lost of locked status sets by user - this.hidden = false; // rails always hidden - this.cursoractive = true; // user can interact with cursors - - this.wheelprevented = false; //prevent mousewheel event - - this.overflowx = self.opt.overflowx; - this.overflowy = self.opt.overflowy; - - this.nativescrollingarea = false; - this.checkarea = 0; - - this.events = []; // event list for unbind - - this.saved = {}; // style saved - - this.delaylist = {}; - this.synclist = {}; - - this.lastdeltax = 0; - this.lastdeltay = 0; - - this.detected = getBrowserDetection(); - - var cap = $.extend({}, this.detected); - - this.canhwscroll = (cap.hastransform && self.opt.hwacceleration); - this.ishwscroll = (this.canhwscroll && self.haswrapper); - - this.hasreversehr = (this.isrtlmode&&!cap.iswebkit); //RTL mode with reverse horizontal axis - - this.istouchcapable = false; // desktop devices with touch screen support - - //## Check WebKit-based desktop with touch support - //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support) - if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) { - this.istouchcapable = true; - cap.cantouch = false; // parse normal desktop events - } - - //## disable MouseLock API on user request - if (!self.opt.enablemouselockapi) { - cap.hasmousecapture = false; - cap.haspointerlock = false; - } - -/* deprecated - this.delayed = function(name, fn, tm, lazy) { - }; -*/ - - this.debounced = function(name, fn, tm) { - var dd = self.delaylist[name]; - self.delaylist[name] = fn; - if (!dd) { - setTimeout(function() { - var fn = self.delaylist[name]; - self.delaylist[name] = false; - fn.call(self); - }, tm); - } - }; - - var _onsync = false; - - this.synched = function(name, fn) { - - function requestSync() { - if (_onsync) return; - setAnimationFrame(function() { - _onsync = false; - for (var nn in self.synclist) { - var fn = self.synclist[nn]; - if (fn) fn.call(self); - self.synclist[nn] = false; - } - }); - _onsync = true; - } - - self.synclist[name] = fn; - requestSync(); - return name; - }; - - this.unsynched = function(name) { - if (self.synclist[name]) self.synclist[name] = false; - }; - - this.css = function(el, pars) { // save & set - for (var n in pars) { - self.saved.css.push([el, n, el.css(n)]); - el.css(n, pars[n]); - } - }; - - this.scrollTop = function(val) { - return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val); - }; - - this.scrollLeft = function(val) { - return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val); - }; - - // derived by by Dan Pupius www.pupius.net - var BezierClass = function(st, ed, spd, p1, p2, p3, p4) { - - this.st = st; - this.ed = ed; - this.spd = spd; - - this.p1 = p1 || 0; - this.p2 = p2 || 1; - this.p3 = p3 || 0; - this.p4 = p4 || 1; - - this.ts = (new Date()).getTime(); - this.df = this.ed - this.st; - }; - BezierClass.prototype = { - B2: function(t) { - return 3 * t * t * (1 - t); - }, - B3: function(t) { - return 3 * t * (1 - t) * (1 - t); - }, - B4: function(t) { - return (1 - t) * (1 - t) * (1 - t); - }, - getNow: function() { - var nw = (new Date()).getTime(); - var pc = 1 - ((nw - this.ts) / this.spd); - var bz = this.B2(pc) + this.B3(pc) + this.B4(pc); - return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz); - }, - update: function(ed, spd) { - this.st = this.getNow(); - this.ed = ed; - this.spd = spd; - this.ts = (new Date()).getTime(); - this.df = this.ed - this.st; - return this; - } - }; - - //derived from http://stackoverflow.com/questions/11236090/ - function getMatrixValues() { - var tr = self.doc.css(cap.trstyle); - if (tr && (tr.substr(0, 6) == "matrix")) { - return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/); - } - return false; - } - - if (this.ishwscroll) { - // hw accelerated scroll - this.doc.translate = { - x: 0, - y: 0, - tx: "0px", - ty: "0px" - }; - - //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/ - if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/ - - this.getScrollTop = function(last) { - if (!last) { - var mtx = getMatrixValues(); - if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10 - if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow(); - } - return self.doc.translate.y; - }; - - this.getScrollLeft = function(last) { - if (!last) { - var mtx = getMatrixValues(); - if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10 - if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow(); - } - return self.doc.translate.x; - }; - - this.notifyScrollEvent = function(el) { - var e = document.createEvent("UIEvents"); - e.initUIEvent("scroll", false, true, window, 1); - e.niceevent = true; - el.dispatchEvent(e); - }; - - var cxscrollleft = (this.isrtlmode) ? 1 : -1; - - if (cap.hastranslate3d && self.opt.enabletranslate3d) { - this.setScrollTop = function(val, silent) { - self.doc.translate.y = val; - self.doc.translate.ty = (val * -1) + "px"; - self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - this.setScrollLeft = function(val, silent) { - self.doc.translate.x = val; - self.doc.translate.tx = (val * cxscrollleft) + "px"; - self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - } else { - this.setScrollTop = function(val, silent) { - self.doc.translate.y = val; - self.doc.translate.ty = (val * -1) + "px"; - self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - this.setScrollLeft = function(val, silent) { - self.doc.translate.x = val; - self.doc.translate.tx = (val * cxscrollleft) + "px"; - self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - } - } else { - // native scroll - this.getScrollTop = function() { - return self.docscroll.scrollTop(); - }; - this.setScrollTop = function(val) { - return self.docscroll.scrollTop(val); - }; - this.getScrollLeft = function() { - if (self.detected.ismozilla && self.isrtlmode) - return Math.abs(self.docscroll.scrollLeft()); - return self.docscroll.scrollLeft(); - }; - this.setScrollLeft = function(val) { - return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val); - }; - } - - this.getTarget = function(e) { - if (!e) return false; - if (e.target) return e.target; - if (e.srcElement) return e.srcElement; - return false; - }; - - this.hasParent = function(e, id) { - if (!e) return false; - var el = e.target || e.srcElement || e || false; - while (el && el.id != id) { - el = el.parentNode || false; - } - return (el !== false); - }; - - function getZIndex() { - var dom = self.win; - if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available - while (dom.length > 0) { - if (dom[0].nodeType == 9) return false; - var zi = dom.css('zIndex'); - if (!isNaN(zi) && zi != 0) return parseInt(zi); - dom = dom.parent(); - } - return false; - } - - //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie - var _convertBorderWidth = { - "thin": 1, - "medium": 3, - "thick": 5 - }; - - function getWidthToPixel(dom, prop, chkheight) { - var wd = dom.css(prop); - var px = parseFloat(wd); - if (isNaN(px)) { - px = _convertBorderWidth[wd] || 0; - var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS - if (self.isie8 && px) px += 1; - return (brd) ? px : 0; - } - return px; - } - - this.getDocumentScrollOffset = function() { - return {top:window.pageYOffset||document.documentElement.scrollTop, - left:window.pageXOffset||document.documentElement.scrollLeft}; - } - - this.getOffset = function() { - if (self.isfixed) { - var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only) - var scrl = self.getDocumentScrollOffset(); - ofs.top-=scrl.top; - ofs.left-=scrl.left; - return ofs; - } - var ww = self.win.offset(); - if (!self.viewport) return ww; - var vp = self.viewport.offset(); - return { - top: ww.top - vp.top,// + self.viewport.scrollTop(), - left: ww.left - vp.left // + self.viewport.scrollLeft() - }; - }; - - this.updateScrollBar = function(len) { - if (self.ishwscroll) { - self.rail.css({ //** - height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom) - }); - if (self.railh) self.railh.css({ //** - width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right) - }); - - } else { - var wpos = self.getOffset(); - var pos = { - top: wpos.top, - left: wpos.left - (self.opt.railpadding.left + self.opt.railpadding.right) - }; - pos.top += getWidthToPixel(self.win, 'border-top-width', true); - pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width'); - - var off = self.opt.railoffset; - if (off) { - if (off.top) pos.top += off.top; - if (self.rail.align && off.left) pos.left += off.left; - } - - if (!self.railslocked) self.rail.css({ - top: pos.top, - left: pos.left, - height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom) - }); - - if (self.zoom) { - self.zoom.css({ - top: pos.top + 1, - left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4 - }); - } - - if (self.railh && !self.railslocked) { - var pos = { - top: wpos.top, - left: wpos.left - }; - var off = self.opt.railhoffset; - if (!!off) { - if (!!off.top) pos.top += off.top; - if (!!off.left) pos.left += off.left; - } - var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true); - var x = pos.left + getWidthToPixel(self.win, 'border-left-width'); - self.railh.css({ - top: y - (self.opt.railpadding.top + self.opt.railpadding.bottom), - left: x, - width: self.railh.width - }); - } - - - } - }; - - this.doRailClick = function(e, dbl, hr) { - var fn, pg, cur, pos; - - if (self.railslocked) return; - self.cancelEvent(e); - - if (dbl) { - fn = (hr) ? self.doScrollLeft : self.doScrollTop; - cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y); - fn(cur); - } else { - fn = (hr) ? self.doScrollLeftBy : self.doScrollBy; - cur = (hr) ? self.scroll.x : self.scroll.y; - pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top; - pg = (hr) ? self.view.w : self.view.h; - fn((cur >= pos) ? pg: -pg);// (cur >= pos) ? fn(pg): fn(-pg); - } - - }; - - self.hasanimationframe = (setAnimationFrame); - self.hascancelanimationframe = (clearAnimationFrame); - - if (!self.hasanimationframe) { - setAnimationFrame = function(fn) { - return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16); - }; // 1000/60)}; - clearAnimationFrame = clearInterval; - } else if (!self.hascancelanimationframe) clearAnimationFrame = function() { - self.cancelAnimationFrame = true; - }; - - this.init = function() { - - self.saved.css = []; - - if (cap.isie7mobile) return true; // SORRY, DO NOT WORK! - if (cap.isoperamini) return true; // SORRY, DO NOT WORK! - - if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, { - '-ms-touch-action': 'none' - }); - - self.zindex = "auto"; - if (!self.ispage && self.opt.zindex == "auto") { - self.zindex = getZIndex() || "auto"; - } else { - self.zindex = self.opt.zindex; - } - - if (!self.ispage && self.zindex != "auto") { - if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex; - } - - if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0 - self.zindex = "auto"; - } - - if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) { - - var cont = self.docscroll; - if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc; - - if (!cap.isie9mobile) self.css(cont, { - 'overflow-y': 'hidden' - }); - - if (self.ispage && cap.isie7) { - if (self.doc[0].nodeName == 'BODY') self.css($("html"), { - 'overflow-y': 'hidden' - }); //IE7 double scrollbar issue - else if (self.doc[0].nodeName == 'HTML') self.css($("body"), { - 'overflow-y': 'hidden' - }); //IE7 double scrollbar issue - } - - if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), { - "-webkit-overflow-scrolling": "touch" - }); //force hw acceleration - - var cursor = $(document.createElement('div')); - cursor.css({ - position: "relative", - top: 0, - "float": "right", - width: self.opt.cursorwidth, - height: "0px", - 'background-color': self.opt.cursorcolor, - border: self.opt.cursorborder, - 'background-clip': 'padding-box', - '-webkit-border-radius': self.opt.cursorborderradius, - '-moz-border-radius': self.opt.cursorborderradius, - 'border-radius': self.opt.cursorborderradius - }); - - cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight()); - - cursor.addClass('nicescroll-cursors'); - - self.cursor = cursor; - - var rail = $(document.createElement('div')); - rail.attr('id', self.id); - rail.addClass('nicescroll-rails nicescroll-rails-vr'); - - var v, a, kp = ["left","right","top","bottom"]; //** - for (var n in kp) { - a = kp[n]; - v = self.opt.railpadding[a]; - (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0; - } - - rail.append(cursor); - - rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth()); - rail.css({ - width: rail.width + "px", - 'zIndex': self.zindex, - "background": self.opt.background, - cursor: "default" - }); - - rail.visibility = true; - rail.scrollable = true; - - rail.align = (self.opt.railalign == "left") ? 0 : 1; - - self.rail = rail; - - self.rail.drag = false; - - var zoom = false; - if (self.opt.boxzoom && !self.ispage && !cap.isieold) { - zoom = document.createElement('div'); - - self.bind(zoom, "click", self.doZoom); - self.bind(zoom, "mouseenter", function() { - self.zoom.css('opacity', self.opt.cursoropacitymax); - }); - self.bind(zoom, "mouseleave", function() { - self.zoom.css('opacity', self.opt.cursoropacitymin); - }); - - self.zoom = $(zoom); - self.zoom.css({ - "cursor": "pointer", - 'z-index': self.zindex, - 'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)', - 'height': 18, - 'width': 18, - 'backgroundPosition': '0px 0px' - }); - if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom); - if (cap.cantouch && self.opt.gesturezoom) { - self.ongesturezoom = function(e) { - if (e.scale > 1.5) self.doZoomIn(e); - if (e.scale < 0.8) self.doZoomOut(e); - return self.cancelEvent(e); - }; - self.bind(self.win, "gestureend", self.ongesturezoom); - } - } - - // init HORIZ - - self.railh = false; - var railh; - - if (self.opt.horizrailenabled) { - - self.css(cont, { - 'overflow-x': 'hidden' - }); - - var cursor = $(document.createElement('div')); - cursor.css({ - position: "absolute", - top: 0, - height: self.opt.cursorwidth, - width: "0px", - 'background-color': self.opt.cursorcolor, - border: self.opt.cursorborder, - 'background-clip': 'padding-box', - '-webkit-border-radius': self.opt.cursorborderradius, - '-moz-border-radius': self.opt.cursorborderradius, - 'border-radius': self.opt.cursorborderradius - }); - - if (cap.isieold) cursor.css({'overflow':'hidden'}); //IE6 horiz scrollbar issue - - cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth()); - - cursor.addClass('nicescroll-cursors'); - - self.cursorh = cursor; - - railh = $(document.createElement('div')); - railh.attr('id', self.id + '-hr'); - railh.addClass('nicescroll-rails nicescroll-rails-hr'); - railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight()); - railh.css({ - height: railh.height + "px", - 'zIndex': self.zindex, - "background": self.opt.background - }); - - railh.append(cursor); - - railh.visibility = true; - railh.scrollable = true; - - railh.align = (self.opt.railvalign == "top") ? 0 : 1; - - self.railh = railh; - - self.railh.drag = false; - - } - - // - - if (self.ispage) { - rail.css({ - position: "fixed", - top: "0px", - height: "100%" - }); - (rail.align) ? rail.css({ - right: "0px" - }): rail.css({ - left: "0px" - }); - self.body.append(rail); - if (self.railh) { - railh.css({ - position: "fixed", - left: "0px", - width: "100%" - }); - (railh.align) ? railh.css({ - bottom: "0px" - }): railh.css({ - top: "0px" - }); - self.body.append(railh); - } - } else { - if (self.ishwscroll) { - if (self.win.css('position') == 'static') self.css(self.win, { - 'position': 'relative' - }); - var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win; - $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled - if (self.zoom) { - self.zoom.css({ - position: "absolute", - top: 1, - right: 0, - "margin-right": rail.width + 4 - }); - bd.append(self.zoom); - } - rail.css({ - position: "absolute", - top: 0 - }); - (rail.align) ? rail.css({ - right: 0 - }): rail.css({ - left: 0 - }); - bd.append(rail); - if (railh) { - railh.css({ - position: "absolute", - left: 0, - bottom: 0 - }); - (railh.align) ? railh.css({ - bottom: 0 - }): railh.css({ - top: 0 - }); - bd.append(railh); - } - } else { - self.isfixed = (self.win.css("position") == "fixed"); - var rlpos = (self.isfixed) ? "fixed" : "absolute"; - - if (!self.isfixed) self.viewport = self.getViewport(self.win[0]); - if (self.viewport) { - self.body = self.viewport; - if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, { - "position": "relative" - }); - } - - rail.css({ - position: rlpos - }); - if (self.zoom) self.zoom.css({ - position: rlpos - }); - self.updateScrollBar(); - self.body.append(rail); - if (self.zoom) self.body.append(self.zoom); - if (self.railh) { - railh.css({ - position: rlpos - }); - self.body.append(railh); - } - } - - if (cap.isios) self.css(self.win, { - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', - '-webkit-touch-callout': 'none' - }); // prevent grey layer on click - - if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div - if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"}); // Webkit outline - //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"}); // Opera 12- to test [TODO] - - } - - if (self.opt.autohidemode === false) { - self.autohidedom = false; - self.rail.css({ - opacity: self.opt.cursoropacitymax - }); - if (self.railh) self.railh.css({ - opacity: self.opt.cursoropacitymax - }); - } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) { - self.autohidedom = $().add(self.rail); - if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor); - if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); - if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh); - } else if (self.opt.autohidemode == "scroll") { - self.autohidedom = $().add(self.rail); - if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); - } else if (self.opt.autohidemode == "cursor") { - self.autohidedom = $().add(self.cursor); - if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh); - } else if (self.opt.autohidemode == "hidden") { - self.autohidedom = false; - self.hide(); - self.railslocked = false; - } - - if (cap.isie9mobile) { - - self.scrollmom = new ScrollMomentumClass2D(self); - - self.onmangotouch = function() { - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true; - - var dfy = py - self.mangotouch.sy; - var dfx = px - self.mangotouch.sx; - var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2))); - if (df == 0) return; - - var dry = (dfy < 0) ? -1 : 1; - var drx = (dfx < 0) ? -1 : 1; - - var tm = +new Date(); - if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy); - - if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) { - self.scrollmom.stop(); - self.scrollmom.reset(px, py); - self.mangotouch.sy = py; - self.mangotouch.ly = py; - self.mangotouch.sx = px; - self.mangotouch.lx = px; - self.mangotouch.dry = dry; - self.mangotouch.drx = drx; - self.mangotouch.tm = tm; - } else { - - self.scrollmom.stop(); - self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy); - self.mangotouch.tm = tm; - - var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px)); - self.mangotouch.ly = py; - self.mangotouch.lx = px; - - if (ds > 2) { - self.mangotouch.lazy = setTimeout(function() { - self.mangotouch.lazy = false; - self.mangotouch.dry = 0; - self.mangotouch.drx = 0; - self.mangotouch.tm = 0; - self.scrollmom.doMomentum(30); - }, 100); - } - } - }; - - var top = self.getScrollTop(); - var lef = self.getScrollLeft(); - self.mangotouch = { - sy: top, - ly: top, - dry: 0, - sx: lef, - lx: lef, - drx: 0, - lazy: false, - tm: 0 - }; - - self.bind(self.docscroll, "scroll", self.onmangotouch); - - } else { - - if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) { - - self.scrollmom = new ScrollMomentumClass2D(self); - - self.ontouchstart = function(e) { - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - - self.hasmoving = false; - - if (!self.railslocked) { - - var tg; - if (cap.hasmstouch) { - tg = (e.target) ? e.target : false; - while (tg) { - var nc = $(tg).getNiceScroll(); - if ((nc.length > 0) && (nc[0].me == self.me)) break; - if (nc.length > 0) return false; - if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break; - tg = (tg.parentNode) ? tg.parentNode : false; - } - } - - self.cancelScroll(); - - tg = self.getTarget(e); - - if (tg) { - var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type)); - if (skp) return self.stopPropagation(e); - } - - if (!("clientX" in e) && ("changedTouches" in e)) { - e.clientX = e.changedTouches[0].clientX; - e.clientY = e.changedTouches[0].clientY; - } - - if (self.forcescreen) { - var le = e; - e = { - "original": (e.original) ? e.original : e - }; - e.clientX = le.screenX; - e.clientY = le.screenY; - } - - self.rail.drag = { - x: e.clientX, - y: e.clientY, - sx: self.scroll.x, - sy: self.scroll.y, - st: self.getScrollTop(), - sl: self.getScrollLeft(), - pt: 2, - dl: false - }; - - if (self.ispage || !self.opt.directionlockdeadzone) { - self.rail.drag.dl = "f"; - } else { - - var view = { - w: $(window).width(), - h: $(window).height() - }; - - var page = { - w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth), - h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - }; - - var maxh = Math.max(0, page.h - view.h); - var maxw = Math.max(0, page.w - view.w); - - if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false; - else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false; - else self.rail.drag.ck = false; - if (!self.rail.drag.ck) self.rail.drag.dl = "f"; - } - - if (self.opt.touchbehavior && self.isiframe && cap.isie) { - var wp = self.win.position(); - self.rail.drag.x += wp.left; - self.rail.drag.y += wp.top; - } - - self.hasmoving = false; - self.lastmouseup = false; - self.scrollmom.reset(e.clientX, e.clientY); - - if (!cap.cantouch && !this.istouchcapable && !e.pointerType) { - - var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false; - if (!ip) { - if (!self.ispage && cap.hasmousecapture) tg.setCapture(); - if (self.opt.touchbehavior) { - if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event - tg._onclick = tg.onclick; - tg.onclick = function(e) { - if (self.hasmoving) return false; - tg._onclick.call(this, e); - }; - } - return self.cancelEvent(e); - } - return self.stopPropagation(e); - } - - if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) { - pc = { - "tg": tg, - "click": false - }; - self.preventclick = pc; - } - - } - } - - }; - - self.ontouchend = function(e) { - if (!self.rail.drag) return true; - if (self.rail.drag.pt == 2) { - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - self.scrollmom.doMomentum(); - self.rail.drag = false; - if (self.hasmoving) { - self.lastmouseup = true; - self.hideCursor(); - if (cap.hasmousecapture) document.releaseCapture(); - if (!cap.cantouch) return self.cancelEvent(e); - } - } - else if (self.rail.drag.pt == 1) { - return self.onmouseup(e); - } - - }; - - var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture); - - self.ontouchmove = function(e, byiframe) { - - if (!self.rail.drag) return false; - - if (e.targetTouches && self.opt.preventmultitouchscrolling) { - if (e.targetTouches.length > 1) return false; // multitouch - } - - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - - if (self.rail.drag.pt == 2) { - if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements - - self.hasmoving = true; - - if (self.preventclick && !self.preventclick.click) { - self.preventclick.click = self.preventclick.tg.onclick || false; - self.preventclick.tg.onclick = self.onpreventclick; - } - - var ev = $.extend({ - "original": e - }, e); - e = ev; - - if (("changedTouches" in e)) { - e.clientX = e.changedTouches[0].clientX; - e.clientY = e.changedTouches[0].clientY; - } - - if (self.forcescreen) { - var le = e; - e = { - "original": (e.original) ? e.original : e - }; - e.clientX = le.screenX; - e.clientY = le.screenY; - } - - var ofy,ofx; - ofx = ofy = 0; - - if (moveneedoffset && !byiframe) { - var wp = self.win.position(); - ofx = -wp.left; - ofy = -wp.top; - } - - var fy = e.clientY + ofy; - var my = (fy - self.rail.drag.y); - var fx = e.clientX + ofx; - var mx = (fx - self.rail.drag.x); - - var ny = self.rail.drag.st - my; - - if (self.ishwscroll && self.opt.bouncescroll) { - if (ny < 0) { - ny = Math.round(ny / 2); - // fy = 0; - } else if (ny > self.page.maxh) { - ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2); - // fy = 0; - } - } else { - if (ny < 0) { - ny = 0; - fy = 0; - } - if (ny > self.page.maxh) { - ny = self.page.maxh; - fy = 0; - } - } - - var nx; - if (self.railh && self.railh.scrollable) { - nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx; - - if (self.ishwscroll && self.opt.bouncescroll) { - if (nx < 0) { - nx = Math.round(nx / 2); - // fx = 0; - } else if (nx > self.page.maxw) { - nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2); - // fx = 0; - } - } else { - if (nx < 0) { - nx = 0; - fx = 0; - } - if (nx > self.page.maxw) { - nx = self.page.maxw; - fx = 0; - } - } - - } - - var grabbed = false; - if (self.rail.drag.dl) { - grabbed = true; - if (self.rail.drag.dl == "v") nx = self.rail.drag.sl; - else if (self.rail.drag.dl == "h") ny = self.rail.drag.st; - } else { - var ay = Math.abs(my); - var ax = Math.abs(mx); - var dz = self.opt.directionlockdeadzone; - if (self.rail.drag.ck == "v") { - if (ay > dz && (ax <= (ay * 0.3))) { - self.rail.drag = false; - return true; - } else if (ax > dz) { - self.rail.drag.dl = "f"; - $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked) - } - } else if (self.rail.drag.ck == "h") { - if (ax > dz && (ay <= (ax * 0.3))) { - self.rail.drag = false; - return true; - } else if (ay > dz) { - self.rail.drag.dl = "f"; - $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked) - } - } - } - - self.synched("touchmove", function() { - if (self.rail.drag && (self.rail.drag.pt == 2)) { - if (self.prepareTransition) self.prepareTransition(0); - if (self.rail.scrollable) self.setScrollTop(ny); - self.scrollmom.update(fx, fy); - if (self.railh && self.railh.scrollable) { - self.setScrollLeft(nx); - self.showCursor(ny, nx); - } else { - self.showCursor(ny); - } - if (cap.isie10) document.selection.clear(); - } - }); - - if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like! - if (grabbed) return self.cancelEvent(e); - } - else if (self.rail.drag.pt == 1) { // drag on cursor - return self.onmousemove(e); - } - - }; - - } - - self.onmousedown = function(e, hronly) { - if (self.rail.drag && self.rail.drag.pt != 1) return; - if (self.railslocked) return self.cancelEvent(e); - self.cancelScroll(); - self.rail.drag = { - x: e.clientX, - y: e.clientY, - sx: self.scroll.x, - sy: self.scroll.y, - pt: 1, - hr: (!!hronly) - }; - var tg = self.getTarget(e); - if (!self.ispage && cap.hasmousecapture) tg.setCapture(); - if (self.isiframe && !cap.hasmousecapture) { - self.saved.csspointerevents = self.doc.css("pointer-events"); - self.css(self.doc, { - "pointer-events": "none" - }); - } - self.hasmoving = false; - return self.cancelEvent(e); - }; - - self.onmouseup = function(e) { - if (self.rail.drag) { - if (self.rail.drag.pt != 1) return true; - if (cap.hasmousecapture) document.releaseCapture(); - if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents); - self.rail.drag = false; - //if (!self.rail.active) self.hideCursor(); - if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning - return self.cancelEvent(e); - } - }; - - self.onmousemove = function(e) { - if (self.rail.drag) { - if (self.rail.drag.pt != 1) return; - - if (cap.ischrome && e.which == 0) return self.onmouseup(e); - - self.cursorfreezed = true; - self.hasmoving = true; - - if (self.rail.drag.hr) { - self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x); - if (self.scroll.x < 0) self.scroll.x = 0; - var mw = self.scrollvaluemaxw; - if (self.scroll.x > mw) self.scroll.x = mw; - } else { - self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y); - if (self.scroll.y < 0) self.scroll.y = 0; - var my = self.scrollvaluemax; - if (self.scroll.y > my) self.scroll.y = my; - } - - self.synched('mousemove', function() { - if (self.rail.drag && (self.rail.drag.pt == 1)) { - self.showCursor(); - if (self.rail.drag.hr) { - if (self.hasreversehr) { - self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed); - } else { - self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed); - } - } - else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed); - } - }); - - return self.cancelEvent(e); - } - /* - else { - self.checkarea = true; - } -*/ - }; - - if (cap.cantouch || self.opt.touchbehavior) { - - self.onpreventclick = function(e) { - if (self.preventclick) { - self.preventclick.tg.onclick = self.preventclick.click; - self.preventclick = false; - return self.cancelEvent(e); - } - } - - self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging - - self.onclick = (cap.isios) ? false : function(e) { - if (self.lastmouseup) { - self.lastmouseup = false; - return self.cancelEvent(e); - } else { - return true; - } - }; - - if (self.opt.grabcursorenabled && cap.cursorgrabvalue) { - self.css((self.ispage) ? self.doc : self.win, { - 'cursor': cap.cursorgrabvalue - }); - self.css(self.rail, { - 'cursor': cap.cursorgrabvalue - }); - } - - } else { - - var checkSelectionScroll = function(e) { - if (!self.selectiondrag) return; - - if (e) { - var ww = self.win.outerHeight(); - var df = (e.pageY - self.selectiondrag.top); - if (df > 0 && df < ww) df = 0; - if (df >= ww) df -= ww; - self.selectiondrag.df = df; - } - if (self.selectiondrag.df == 0) return; - - var rt = -Math.floor(self.selectiondrag.df / 6) * 2; - self.doScrollBy(rt); - - self.debounced("doselectionscroll", function() { - checkSelectionScroll() - }, 50); - }; - - if ("getSelection" in document) { // A grade - Major browsers - self.hasTextSelected = function() { - return (document.getSelection().rangeCount > 0); - }; - } else if ("selection" in document) { //IE9- - self.hasTextSelected = function() { - return (document.selection.type != "None"); - }; - } else { - self.hasTextSelected = function() { // no support - return false; - }; - } - - self.onselectionstart = function(e) { -/* More testing - severe chrome issues - if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling - self.win.css({'overflow':'auto'}); - setTimeout(function(){ - self.win.css({'overflow':''}); - },10); - return true; - } -*/ - if (self.ispage) return; - self.selectiondrag = self.win.offset(); - }; - - self.onselectionend = function(e) { - self.selectiondrag = false; - }; - self.onselectiondrag = function(e) { - if (!self.selectiondrag) return; - if (self.hasTextSelected()) self.debounced("selectionscroll", function() { - checkSelectionScroll(e) - }, 250); - }; - - - } - - if (cap.hasw3ctouch) { //IE11+ - self.css(self.rail, { - 'touch-action': 'none' - }); - self.css(self.cursor, { - 'touch-action': 'none' - }); - self.bind(self.win, "pointerdown", self.ontouchstart); - self.bind(document, "pointerup", self.ontouchend); - self.bind(document, "pointermove", self.ontouchmove); - } else if (cap.hasmstouch) { //IE10 - self.css(self.rail, { - '-ms-touch-action': 'none' - }); - self.css(self.cursor, { - '-ms-touch-action': 'none' - }); - self.bind(self.win, "MSPointerDown", self.ontouchstart); - self.bind(document, "MSPointerUp", self.ontouchend); - self.bind(document, "MSPointerMove", self.ontouchmove); - self.bind(self.cursor, "MSGestureHold", function(e) { - e.preventDefault() - }); - self.bind(self.cursor, "contextmenu", function(e) { - e.preventDefault() - }); - } else if (this.istouchcapable) { //desktop with screen touch enabled - self.bind(self.win, "touchstart", self.ontouchstart); - self.bind(document, "touchend", self.ontouchend); - self.bind(document, "touchcancel", self.ontouchend); - self.bind(document, "touchmove", self.ontouchmove); - } - - - if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) { - - self.rail.css({ - "cursor": "default" - }); - self.railh && self.railh.css({ - "cursor": "default" - }); - - self.jqbind(self.rail, "mouseenter", function() { - if (!self.ispage && !self.win.is(":visible")) return false; - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.rail, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - - if (self.opt.sensitiverail) { - self.bind(self.rail, "click", function(e) { - self.doRailClick(e, false, false) - }); - self.bind(self.rail, "dblclick", function(e) { - self.doRailClick(e, true, false) - }); - self.bind(self.cursor, "click", function(e) { - self.cancelEvent(e) - }); - self.bind(self.cursor, "dblclick", function(e) { - self.cancelEvent(e) - }); - } - - if (self.railh) { - self.jqbind(self.railh, "mouseenter", function() { - if (!self.ispage && !self.win.is(":visible")) return false; - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.railh, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - - if (self.opt.sensitiverail) { - self.bind(self.railh, "click", function(e) { - self.doRailClick(e, false, true) - }); - self.bind(self.railh, "dblclick", function(e) { - self.doRailClick(e, true, true) - }); - self.bind(self.cursorh, "click", function(e) { - self.cancelEvent(e) - }); - self.bind(self.cursorh, "dblclick", function(e) { - self.cancelEvent(e) - }); - } - - } - - } - - if (!cap.cantouch && !self.opt.touchbehavior) { - - self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup); - self.bind(document, "mousemove", self.onmousemove); - if (self.onclick) self.bind(document, "click", self.onclick); - - self.bind(self.cursor, "mousedown", self.onmousedown); - self.bind(self.cursor, "mouseup", self.onmouseup); - - if (self.railh) { - self.bind(self.cursorh, "mousedown", function(e) { - self.onmousedown(e, true) - }); - self.bind(self.cursorh, "mouseup", self.onmouseup); - } - - if (!self.ispage && self.opt.enablescrollonselection) { - self.bind(self.win[0], "mousedown", self.onselectionstart); - self.bind(document, "mouseup", self.onselectionend); - self.bind(self.cursor, "mouseup", self.onselectionend); - if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend); - self.bind(document, "mousemove", self.onselectiondrag); - } - - if (self.zoom) { - self.jqbind(self.zoom, "mouseenter", function() { - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.zoom, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - } - - } else { - - self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend); - self.bind(document, "mousemove", self.ontouchmove); - if (self.onclick) self.bind(document, "click", self.onclick); - - if (self.opt.cursordragontouch) { - self.bind(self.cursor, "mousedown", self.onmousedown); - self.bind(self.cursor, "mouseup", self.onmouseup); - //self.bind(self.cursor, "mousemove", self.onmousemove); - self.cursorh && self.bind(self.cursorh, "mousedown", function(e) { - self.onmousedown(e, true) - }); - //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove); - self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup); - } - - } - - if (self.opt.enablemousewheel) { - if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel); - self.bind(self.rail, "mousewheel", self.onmousewheel); - if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr); - } - - if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) { - if (!self.win.attr("tabindex")) self.win.attr({ - "tabindex": tabindexcounter++ - }); - - self.jqbind(self.win, "focus", function(e) { - domfocus = (self.getTarget(e)).id || true; - self.hasfocus = true; - if (self.canshowonmouseevent) self.noticeCursor(); - }); - self.jqbind(self.win, "blur", function(e) { - domfocus = false; - self.hasfocus = false; - }); - - self.jqbind(self.win, "mouseenter", function(e) { - mousefocus = (self.getTarget(e)).id || true; - self.hasmousefocus = true; - if (self.canshowonmouseevent) self.noticeCursor(); - }); - self.jqbind(self.win, "mouseleave", function() { - mousefocus = false; - self.hasmousefocus = false; - if (!self.rail.drag) self.hideCursor(); - }); - - } - - } // !ie9mobile - - //Thanks to http://www.quirksmode.org !! - self.onkeypress = function(e) { - if (self.railslocked && self.page.maxh == 0) return true; - - e = (e) ? e : window.e; - var tg = self.getTarget(e); - if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) { - var tp = tg.getAttribute('type') || tg.type || false; - if ((!tp) || !(/submit|button|cancel/i.tp)) return true; - } - - if ($(tg).attr('contenteditable')) return true; - - if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) { - var key = e.keyCode; - - if (self.railslocked && key != 27) return self.cancelEvent(e); - - var ctrl = e.ctrlKey || false; - var shift = e.shiftKey || false; - - var ret = false; - switch (key) { - case 38: - case 63233: //safari - self.doScrollBy(24 * 3); - ret = true; - break; - case 40: - case 63235: //safari - self.doScrollBy(-24 * 3); - ret = true; - break; - case 37: - case 63232: //safari - if (self.railh) { - (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3); - ret = true; - } - break; - case 39: - case 63234: //safari - if (self.railh) { - (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3); - ret = true; - } - break; - case 33: - case 63276: // safari - self.doScrollBy(self.view.h); - ret = true; - break; - case 34: - case 63277: // safari - self.doScrollBy(-self.view.h); - ret = true; - break; - case 36: - case 63273: // safari - (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0); - ret = true; - break; - case 35: - case 63275: // safari - (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh); - ret = true; - break; - case 32: - if (self.opt.spacebarenabled) { - (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h); - ret = true; - } - break; - case 27: // ESC - if (self.zoomactive) { - self.doZoom(); - ret = true; - } - break; - } - if (ret) return self.cancelEvent(e); - } - }; - - if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress); - - self.bind(document, "keydown", function(e) { - var ctrl = e.ctrlKey || false; - if (ctrl) self.wheelprevented = true; - }); - self.bind(document, "keyup", function(e) { - var ctrl = e.ctrlKey || false; - if (!ctrl) self.wheelprevented = false; - }); - self.bind(window,"blur",function(e){ - self.wheelprevented = false; - }); - - self.bind(window, 'resize', self.lazyResize); - self.bind(window, 'orientationchange', self.lazyResize); - - self.bind(window, "load", self.lazyResize); - - if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26 - var tmp = self.win.attr("style"); - var ww = parseFloat(self.win.css("width")) + 1; - self.win.css('width', ww); - self.synched("chromefix", function() { - self.win.attr("style", tmp) - }); - } - - - // Trying a cross-browser implementation - good luck! - - self.onAttributeChange = function(e) { - self.lazyResize(self.isieold ? 250 : 30); - }; - - if (ClsMutationObserver !== false) { - self.observerbody = new ClsMutationObserver(function(mutations) { - mutations.forEach(function(mut){ - if (mut.type=="attributes") { - return ($("body").hasClass("modal-open")) ? self.hide() : self.show(); // Support for Bootstrap modal - } - }); - if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30); - }); - self.observerbody.observe(document.body, { - childList: true, - subtree: true, - characterData: false, - attributes: true, - attributeFilter: ['class'] - }); - } - - if (!self.ispage && !self.haswrapper) { - // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content - if (ClsMutationObserver !== false) { - self.observer = new ClsMutationObserver(function(mutations) { - mutations.forEach(self.onAttributeChange); - }); - self.observer.observe(self.win[0], { - childList: true, - characterData: false, - attributes: true, - subtree: false - }); - self.observerremover = new ClsMutationObserver(function(mutations) { - mutations.forEach(function(mo) { - if (mo.removedNodes.length > 0) { - for (var dd in mo.removedNodes) { - if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove(); - } - } - }); - }); - self.observerremover.observe(self.win[0].parentNode, { - childList: true, - characterData: false, - attributes: false, - subtree: false - }); - } else { - self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange); - if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug - self.bind(self.win, "DOMNodeRemoved", function(e) { - if (e.target == self.win[0]) self.remove(); - }); - } - } - - // - - if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom); - if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize); - - // self.checkrtlmode = true; - self.lazyResize(30); - - } - - if (this.doc[0].nodeName == 'IFRAME') { - var oniframeload = function() { - self.iframexd = false; - var doc; - try { - doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document; - var a = doc.domain; - } catch (e) { - self.iframexd = true; - doc = false - } - - if (self.iframexd) { - if ("console" in window) console.log('NiceScroll error: policy restriced iframe'); - return true; //cross-domain - I can't manage this - } - - self.forcescreen = true; - - if (self.isiframe) { - self.iframe = { - "doc": $(doc), - "html": self.doc.contents().find('html')[0], - "body": self.doc.contents().find('body')[0] - }; - self.getContentSize = function() { - return { - w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth), - h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight) - }; - }; - self.docscroll = $(self.iframe.body); //$(this.contentWindow); - } - - if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) { - self.win.scrollTop(0); // reset position - self.doc.height(""); //reset height to fix browser bug - var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight); - self.doc.height(hh); - } - self.lazyResize(30); - - if (cap.isie7) self.css($(self.iframe.html), { - 'overflow-y': 'hidden' - }); - self.css($(self.iframe.body), { - 'overflow-y': 'hidden' - }); - - if (cap.isios && self.haswrapper) { - self.css($(doc.body), { - '-webkit-transform': 'translate3d(0,0,0)' - }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/ - } - - if ('contentWindow' in this) { - self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor - } else { - self.bind(doc, "scroll", self.onscroll); - } - - if (self.opt.enablemousewheel) { - self.bind(doc, "mousewheel", self.onmousewheel); - } - - if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress); - - if (cap.cantouch || self.opt.touchbehavior) { - self.bind(doc, "mousedown", self.ontouchstart); - self.bind(doc, "mousemove", function(e) { - return self.ontouchmove(e, true) - }); - if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), { - 'cursor': cap.cursorgrabvalue - }); - } - - self.bind(doc, "mouseup", self.ontouchend); - - if (self.zoom) { - if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom); - if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom); - } - }; - - if (this.doc[0].readyState && this.doc[0].readyState == "complete") { - setTimeout(function() { - oniframeload.call(self.doc[0], false) - }, 500); - } - self.bind(this.doc, "load", oniframeload); - - } - - }; - - this.showCursor = function(py, px) { - if (self.cursortimeout) { - clearTimeout(self.cursortimeout); - self.cursortimeout = 0; - } - if (!self.rail) return; - if (self.autohidedom) { - self.autohidedom.stop().css({ - opacity: self.opt.cursoropacitymax - }); - self.cursoractive = true; - } - - if (!self.rail.drag || self.rail.drag.pt != 1) { - if ((typeof py != "undefined") && (py !== false)) { - self.scroll.y = Math.round(py * 1 / self.scrollratio.y); - } - if (typeof px != "undefined") { - self.scroll.x = Math.round(px * 1 / self.scrollratio.x); - } - } - - self.cursor.css({ - height: self.cursorheight, - top: self.scroll.y - }); - if (self.cursorh) { - var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x; - (!self.rail.align && self.rail.visibility) ? self.cursorh.css({ - width: self.cursorwidth, - left: lx + self.rail.width - }): self.cursorh.css({ - width: self.cursorwidth, - left: lx - }); - self.cursoractive = true; - } - - if (self.zoom) self.zoom.stop().css({ - opacity: self.opt.cursoropacitymax - }); - }; - - this.hideCursor = function(tm) { - if (self.cursortimeout) return; - if (!self.rail) return; - if (!self.autohidedom) return; - if (self.hasmousefocus && self.opt.autohidemode == "leave") return; - self.cursortimeout = setTimeout(function() { - if (!self.rail.active || !self.showonmouseevent) { - self.autohidedom.stop().animate({ - opacity: self.opt.cursoropacitymin - }); - if (self.zoom) self.zoom.stop().animate({ - opacity: self.opt.cursoropacitymin - }); - self.cursoractive = false; - } - self.cursortimeout = 0; - }, tm || self.opt.hidecursordelay); - }; - - this.noticeCursor = function(tm, py, px) { - self.showCursor(py, px); - if (!self.rail.active) self.hideCursor(tm); - }; - - this.getContentSize = - (self.ispage) ? - function() { - return { - w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth), - h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - } - } : (self.haswrapper) ? - function() { - return { - w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')), - h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom')) - } - } : function() { - return { - w: self.docscroll[0].scrollWidth, - h: self.docscroll[0].scrollHeight - } - }; - - this.onResize = function(e, page) { - - if (!self || !self.win) return false; - - if (!self.haswrapper && !self.ispage) { - if (self.win.css('display') == 'none') { - if (self.visibility) self.hideRail().hideRailHr(); - return false; - } else { - if (!self.hidden && !self.visibility) self.showRail().showRailHr(); - } - } - - var premaxh = self.page.maxh; - var premaxw = self.page.maxw; - - var preview = { - h: self.view.h, - w: self.view.w - }; - - self.view = { - w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth), - h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight) - }; - - self.page = (page) ? page : self.getContentSize(); - - self.page.maxh = Math.max(0, self.page.h - self.view.h); - self.page.maxw = Math.max(0, self.page.w - self.view.w); - - if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) { - // test position - if (!self.ispage) { - var pos = self.win.offset(); - if (self.lastposition) { - var lst = self.lastposition; - if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do - } - self.lastposition = pos; - } else { - return self; //nothing to do - } - } - - if (self.page.maxh == 0) { - self.hideRail(); - self.scrollvaluemax = 0; - self.scroll.y = 0; - self.scrollratio.y = 0; - self.cursorheight = 0; - self.setScrollTop(0); - self.rail.scrollable = false; - } else { - self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom); //** - self.rail.scrollable = true; - } - - if (self.page.maxw == 0) { - self.hideRailHr(); - self.scrollvaluemaxw = 0; - self.scroll.x = 0; - self.scrollratio.x = 0; - self.cursorwidth = 0; - self.setScrollLeft(0); - self.railh.scrollable = false; - } else { - self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right); //** - self.railh.scrollable = true; - } - - self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0)); - if (self.railslocked) { - if (!self.ispage) self.updateScrollBar(self.view); - return false; - } - - if (!self.hidden && !self.visibility) { - self.showRail().showRailHr(); - } - else if (!self.hidden && !self.railh.visibility) self.showRailHr(); - - if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20; - - self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h))); - self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight); - - self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w))); - self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth); - - self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom); //** - - if (self.railh) { - self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w; - self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right); //** - } - - /* - if (self.checkrtlmode&&self.railh) { - self.checkrtlmode = false; - if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw); - } -*/ - - if (!self.ispage) self.updateScrollBar(self.view); - - self.scrollratio = { - x: (self.page.maxw / self.scrollvaluemaxw), - y: (self.page.maxh / self.scrollvaluemax) - }; - - var sy = self.getScrollTop(); - if (sy > self.page.maxh) { - self.doScrollTop(self.page.maxh); - } else { - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x)); - if (self.cursoractive) self.noticeCursor(); - } - - if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y)); - - return self; - }; - - this.resize = self.onResize; - - this.lazyResize = function(tm) { // event debounce - tm = (isNaN(tm)) ? 30 : tm; - self.debounced('resize', self.resize, tm); - return self; - }; - - // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel - function _modernWheelEvent(dom, name, fn, bubble) { - self._bind(dom, name, function(e) { - var e = (e) ? e : window.event; - var event = { - original: e, - target: e.target || e.srcElement, - type: "wheel", - deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1, - deltaX: 0, - deltaZ: 0, - preventDefault: function() { - e.preventDefault ? e.preventDefault() : e.returnValue = false; - return false; - }, - stopImmediatePropagation: function() { - (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true; - } - }; - - if (name == "mousewheel") { - event.deltaY = -1 / 40 * e.wheelDelta; - e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX); - } else { - event.deltaY = e.detail; - } - - return fn.call(dom, event); - }, bubble); - }; - - - - this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave) - self.events.push({ - e: dom, - n: name, - f: fn, - q: true - }); - $(dom).bind(name, fn); - }; - - this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind - var el = ("jquery" in dom) ? dom[0] : dom; - - if (name == 'mousewheel') { - if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix - self._bind(el, "wheel", fn, bubble || false); - } else { - var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox - _modernWheelEvent(el, wname, fn, bubble || false); - if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy - } - } else if (el.addEventListener) { - if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support - var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove'; - self._bind(el, tt, function(e) { - if (e.touches) { - if (e.touches.length < 2) { - var ev = (e.touches.length) ? e.touches[0] : e; - ev.original = e; - fn.call(this, ev); - } - } else if (e.changedTouches) { - var ev = e.changedTouches[0]; - ev.original = e; - fn.call(this, ev); - } //blackberry - }, bubble || false); - } - self._bind(el, name, fn, bubble || false); - if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false); - } else { - self._bind(el, name, function(e) { - e = e || window.event || false; - if (e) { - if (e.srcElement) e.target = e.srcElement; - } - if (!("pageY" in e)) { - e.pageX = e.clientX + document.documentElement.scrollLeft; - e.pageY = e.clientY + document.documentElement.scrollTop; - } - return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true; - }); - } - }; - - if (cap.haseventlistener) { // W3C standard model - this._bind = function(el, name, fn, bubble) { // primitive bind - self.events.push({ - e: el, - n: name, - f: fn, - b: bubble, - q: false - }); - el.addEventListener(name, fn, bubble || false); - }; - this.cancelEvent = function(e) { - if (!e) return false; - var e = (e.original) ? e.original : e; - e.preventDefault(); - e.stopPropagation(); - if (e.preventManipulation) e.preventManipulation(); //IE10 - return false; - }; - this.stopPropagation = function(e) { - if (!e) return false; - var e = (e.original) ? e.original : e; - e.stopPropagation(); - return false; - }; - this._unbind = function(el, name, fn, bub) { // primitive unbind - el.removeEventListener(name, fn, bub); - }; - } else { // old IE model - this._bind = function(el, name, fn, bubble) { // primitive bind - self.events.push({ - e: el, - n: name, - f: fn, - b: bubble, - q: false - }); - if (el.attachEvent) { - el.attachEvent("on" + name, fn); - } else { - el["on" + name] = fn; - } - }; - // Thanks to http://www.switchonthecode.com !! - this.cancelEvent = function(e) { - var e = window.event || false; - if (!e) return false; - e.cancelBubble = true; - e.cancel = true; - e.returnValue = false; - return false; - }; - this.stopPropagation = function(e) { - var e = window.event || false; - if (!e) return false; - e.cancelBubble = true; - return false; - }; - this._unbind = function(el, name, fn, bub) { // primitive unbind IE old - if (el.detachEvent) { - el.detachEvent('on' + name, fn); - } else { - el['on' + name] = false; - } - }; - } - - this.unbindAll = function() { - for (var a = 0; a < self.events.length; a++) { - var r = self.events[a]; - (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b); - } - }; - - this.showRail = function() { - if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) { - self.visibility = true; - self.rail.visibility = true; - self.rail.css('display', 'block'); - } - return self; - }; - - this.showRailHr = function() { - if (!self.railh) return self; - if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) { - self.railh.visibility = true; - self.railh.css('display', 'block'); - } - return self; - }; - - this.hideRail = function() { - self.visibility = false; - self.rail.visibility = false; - self.rail.css('display', 'none'); - return self; - }; - - this.hideRailHr = function() { - if (!self.railh) return self; - self.railh.visibility = false; - self.railh.css('display', 'none'); - return self; - }; - - this.show = function() { - self.hidden = false; - self.railslocked = false; - return self.showRail().showRailHr(); - }; - - this.hide = function() { - self.hidden = true; - self.railslocked = true; - return self.hideRail().hideRailHr(); - }; - - this.toggle = function() { - return (self.hidden) ? self.show() : self.hide(); - }; - - this.remove = function() { - self.stop(); - if (self.cursortimeout) clearTimeout(self.cursortimeout); - self.doZoomOut(); - self.unbindAll(); - - if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug - - if (self.observer !== false) self.observer.disconnect(); - if (self.observerremover !== false) self.observerremover.disconnect(); - if (self.observerbody !== false) self.observerbody.disconnect(); - - self.events = null; - - if (self.cursor) { - self.cursor.remove(); - } - if (self.cursorh) { - self.cursorh.remove(); - } - if (self.rail) { - self.rail.remove(); - } - if (self.railh) { - self.railh.remove(); - } - if (self.zoom) { - self.zoom.remove(); - } - for (var a = 0; a < self.saved.css.length; a++) { - var d = self.saved.css[a]; - d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]); - } - self.saved = false; - self.me.data('__nicescroll', ''); //erase all traces - - // memory leak fixed by GianlucaGuarini - thanks a lot! - // remove the current nicescroll from the $.nicescroll array & normalize array - var lst = $.nicescroll; - lst.each(function(i) { - if (!this) return; - if (this.id === self.id) { - delete lst[i]; - for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b]; - lst.length--; - if (lst.length) delete lst[lst.length]; - } - }); - - for (var i in self) { - self[i] = null; - delete self[i]; - } - - self = null; - - }; - - this.scrollstart = function(fn) { - this.onscrollstart = fn; - return self; - }; - this.scrollend = function(fn) { - this.onscrollend = fn; - return self; - }; - this.scrollcancel = function(fn) { - this.onscrollcancel = fn; - return self; - }; - - this.zoomin = function(fn) { - this.onzoomin = fn; - return self; - }; - this.zoomout = function(fn) { - this.onzoomout = fn; - return self; - }; - - this.isScrollable = function(e) { - var dom = (e.target) ? e.target : e; - if (dom.nodeName == 'OPTION') return true; - while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) { - var dd = $(dom); - var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || ''; - if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight); - dom = (dom.parentNode) ? dom.parentNode : false; - } - return false; - }; - - this.getViewport = function(me) { - var dom = (me && me.parentNode) ? me.parentNode : false; - while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) { - var dd = $(dom); - if (/fixed|absolute/.test(dd.css("position"))) return dd; - var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || ''; - if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd; - if (dd.getNiceScroll().length > 0) return dd; - dom = (dom.parentNode) ? dom.parentNode : false; - } - return false; //(dom) ? $(dom) : false; - }; - - this.triggerScrollEnd = function() { - if (!self.onscrollend) return; - - var px = self.getScrollLeft(); - var py = self.getScrollTop(); - - var info = { - "type": "scrollend", - "current": { - "x": px, - "y": py - }, - "end": { - "x": px, - "y": py - } - }; - self.onscrollend.call(self, info); - } - - function execScrollWheel(e, hr, chkscroll) { - var px, py; - - if (e.deltaMode == 0) { // PIXEL - px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3))); - py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3))); - } else if (e.deltaMode == 1) { // LINE - px = -Math.floor(e.deltaX * self.opt.mousescrollstep); - py = -Math.floor(e.deltaY * self.opt.mousescrollstep); - } - - if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support - px = py; - py = 0; - - if (chkscroll) { - var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0); - if (hrend) { // preserve vertical scrolling - py = px; - px = 0; - } - } - - } - - if (px) { - if (self.scrollmom) { - self.scrollmom.stop() - } - self.lastdeltax += px; - self.debounced("mousewheelx", function() { - var dt = self.lastdeltax; - self.lastdeltax = 0; - if (!self.rail.drag) { - self.doScrollLeftBy(dt) - } - }, 15); - } - if (py) { - if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) { - if (py < 0) { - if (self.getScrollTop() >= self.page.maxh) return true; - } else { - if (self.getScrollTop() <= 0) return true; - } - } - if (self.scrollmom) { - self.scrollmom.stop() - } - self.lastdeltay += py; - self.debounced("mousewheely", function() { - var dt = self.lastdeltay; - self.lastdeltay = 0; - if (!self.rail.drag) { - self.doScrollBy(dt) - } - }, 15); - } - - e.stopImmediatePropagation(); - return e.preventDefault(); - }; - - this.onmousewheel = function(e) { - if (self.wheelprevented) return; - if (self.railslocked) { - self.debounced("checkunlock", self.resize, 250); - return true; - } - if (self.rail.drag) return self.cancelEvent(e); - - if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant) - - if (self.opt.oneaxismousemode && e.deltaX == 0) { - if (!self.rail.scrollable) { - if (self.railh && self.railh.scrollable) { - return self.onmousewheelhr(e); - } else { - return true; - } - } - } - - var nw = +(new Date()); - var chk = false; - if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { - self.nativescrollingarea = self.isScrollable(e); - chk = true; - } - self.checkarea = nw; - if (self.nativescrollingarea) return true; // this isn't my business - var ret = execScrollWheel(e, false, chk); - if (ret) self.checkarea = 0; - return ret; - }; - - this.onmousewheelhr = function(e) { - if (self.wheelprevented) return; - if (self.railslocked || !self.railh.scrollable) return true; - if (self.rail.drag) return self.cancelEvent(e); - - var nw = +(new Date()); - var chk = false; - if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { - self.nativescrollingarea = self.isScrollable(e); - chk = true; - } - self.checkarea = nw; - if (self.nativescrollingarea) return true; // this isn't my business - if (self.railslocked) return self.cancelEvent(e); - - return execScrollWheel(e, true, chk); - }; - - this.stop = function() { - self.cancelScroll(); - if (self.scrollmon) self.scrollmon.stop(); - self.cursorfreezed = false; - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - self.noticeCursor(); - return self; - }; - - this.getTransitionSpeed = function(dif) { - var sp = Math.round(self.opt.scrollspeed * 10); - var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed)); - return (ex > 20) ? ex : 0; - }; - - if (!self.opt.smoothscroll) { - this.doScrollLeft = function(x, spd) { //direct - var y = self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - this.doScrollTop = function(y, spd) { //direct - var x = self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - this.doScrollPos = function(x, y, spd) { //direct - var nx = (x > self.page.maxw) ? self.page.maxw : x; - if (nx < 0) nx = 0; - var ny = (y > self.page.maxh) ? self.page.maxh : y; - if (ny < 0) ny = 0; - self.synched('scroll', function() { - self.setScrollTop(ny); - self.setScrollLeft(nx); - }); - }; - this.cancelScroll = function() {}; // direct - } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) { - this.prepareTransition = function(dif, istime) { - var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif); - var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : ''; - if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) { - self.lasttransitionstyle = trans; - self.doc.css(cap.transitionstyle, trans); - } - return ex; - }; - - this.doScrollLeft = function(x, spd) { //trans - var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollTop = function(y, spd) { //trans - var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollPos = function(x, y, spd) { //trans - - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection - - if (self.opt.bouncescroll == false) { - if (y < 0) y = 0; - else if (y > self.page.maxh) y = self.page.maxh; - if (x < 0) x = 0; - else if (x > self.page.maxw) x = self.page.maxw; - } - - if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false; - - self.newscrolly = y; - self.newscrollx = x; - - self.newscrollspeed = spd || false; - - if (self.timer) return false; - - self.timer = setTimeout(function() { - - var top = self.getScrollTop(); - var lft = self.getScrollLeft(); - - var dst = {}; - dst.x = x - lft; - dst.y = y - top; - dst.px = lft; - dst.py = top; - - var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2))); - var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd); - if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed; - - self.prepareTransition(ms, true); - - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - - if (ms > 0) { - - if (!self.scrollrunning && self.onscrollstart) { - var info = { - "type": "scrollstart", - "current": { - "x": lft, - "y": top - }, - "request": { - "x": x, - "y": y - }, - "end": { - "x": self.newscrollx, - "y": self.newscrolly - }, - "speed": ms - }; - self.onscrollstart.call(self, info); - } - - if (cap.transitionend) { - if (!self.scrollendtrapped) { - self.scrollendtrapped = true; - self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!! - } - } else { - if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped); - self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event - } - - var py = top; - var px = lft; - self.timerscroll = { - bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1), - bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1) - }; - if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() { - self.showCursor(self.getScrollTop(), self.getScrollLeft()) - }, 60); - - } - - self.synched("doScroll-set", function() { - self.timer = 0; - if (self.scrollendtrapped) self.scrollrunning = true; - self.setScrollTop(self.newscrolly); - self.setScrollLeft(self.newscrollx); - if (!self.scrollendtrapped) self.onScrollTransitionEnd(); - }); - - - }, 50); - - }; - - this.cancelScroll = function() { - if (!self.scrollendtrapped) return true; - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - self.scrollrunning = false; - if (!cap.transitionend) clearTimeout(cap.transitionend); - self.scrollendtrapped = false; - self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd); - self.prepareTransition(0); - self.setScrollTop(py); // fire event onscroll - if (self.railh) self.setScrollLeft(px); - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - self.timerscroll = false; - - self.cursorfreezed = false; - - self.showCursor(py, px); - return self; - }; - this.onScrollTransitionEnd = function() { - if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd); - self.scrollendtrapped = false; - self.prepareTransition(0); - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - self.timerscroll = false; - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - self.setScrollTop(py); // fire event onscroll - if (self.railh) self.setScrollLeft(px); // fire event onscroll left - - self.noticeCursor(false, py, px); - - self.cursorfreezed = false; - - if (py < 0) py = 0 - else if (py > self.page.maxh) py = self.page.maxh; - if (px < 0) px = 0 - else if (px > self.page.maxw) px = self.page.maxw; - if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed); - - if (self.onscrollend && self.scrollrunning) { - self.triggerScrollEnd(); - } - self.scrollrunning = false; - - }; - - } else { - - this.doScrollLeft = function(x, spd) { //no-trans - var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollTop = function(y, spd) { //no-trans - var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollPos = function(x, y, spd) { //no-trans - var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y; - - if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true; - - if (self.timer) clearAnimationFrame(self.timer); - self.timer = 0; - - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection - - self.newscrolly = y; - self.newscrollx = x; - - if (!self.bouncescroll || !self.rail.visibility) { - if (self.newscrolly < 0) { - self.newscrolly = 0; - } else if (self.newscrolly > self.page.maxh) { - self.newscrolly = self.page.maxh; - } - } - if (!self.bouncescroll || !self.railh.visibility) { - if (self.newscrollx < 0) { - self.newscrollx = 0; - } else if (self.newscrollx > self.page.maxw) { - self.newscrollx = self.page.maxw; - } - } - - self.dst = {}; - self.dst.x = x - px; - self.dst.y = y - py; - self.dst.px = px; - self.dst.py = py; - - var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2))); - - self.dst.ax = self.dst.x / dst; - self.dst.ay = self.dst.y / dst; - - var pa = 0; - var pe = dst; - - if (self.dst.x == 0) { - pa = py; - pe = y; - self.dst.ay = 1; - self.dst.py = 0; - } else if (self.dst.y == 0) { - pa = px; - pe = x; - self.dst.ax = 1; - self.dst.px = 0; - } - - var ms = self.getTransitionSpeed(dst); - if (spd && spd <= 1) ms *= spd; - if (ms > 0) { - self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1); - } else { - self.bzscroll = false; - } - - if (self.timer) return; - - if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize(); - - var sync = 1; - - function scrolling() { - if (self.cancelAnimationFrame) return true; - - self.scrollrunning = true; - - sync = 1 - sync; - if (sync) return (self.timer = setAnimationFrame(scrolling) || 1); - - var done = 0; - var sx, sy; - - var sc = sy = self.getScrollTop(); - if (self.dst.ay) { - sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly; - var dr = sc - sy; - if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly; - self.setScrollTop(sc); - if (sc == self.newscrolly) done = 1; - } else { - done = 1; - } - - var scx = sx = self.getScrollLeft(); - if (self.dst.ax) { - scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx; - var dr = scx - sx; - if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx; - self.setScrollLeft(scx); - if (scx == self.newscrollx) done += 1; - } else { - done += 1; - } - - if (done == 2) { - self.timer = 0; - self.cursorfreezed = false; - self.bzscroll = false; - self.scrollrunning = false; - if (sc < 0) sc = 0; - else if (sc > self.page.maxh) sc = self.page.maxh; - if (scx < 0) scx = 0; - else if (scx > self.page.maxw) scx = self.page.maxw; - if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc); - else { - if (self.onscrollend) { - self.triggerScrollEnd(); - } - } - } else { - self.timer = setAnimationFrame(scrolling) || 1; - } - }; - self.cancelAnimationFrame = false; - self.timer = 1; - - if (self.onscrollstart && !self.scrollrunning) { - var info = { - "type": "scrollstart", - "current": { - "x": px, - "y": py - }, - "request": { - "x": x, - "y": y - }, - "end": { - "x": self.newscrollx, - "y": self.newscrolly - }, - "speed": ms - }; - self.onscrollstart.call(self, info); - } - - scrolling(); - - if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize(); - - self.noticeCursor(); - }; - - this.cancelScroll = function() { - if (self.timer) clearAnimationFrame(self.timer); - self.timer = 0; - self.bzscroll = false; - self.scrollrunning = false; - return self; - }; - - } - - this.doScrollBy = function(stp, relative) { - var ny = 0; - if (relative) { - ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y) - } else { - var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true); - ny = sy - stp; - } - if (self.bouncescroll) { - var haf = Math.round(self.view.h / 2); - if (ny < -haf) ny = -haf - else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf); - } - self.cursorfreezed = false; - - var py = self.getScrollTop(true); - if (ny < 0 && py <= 0) return self.noticeCursor(); - else if (ny > self.page.maxh && py >= self.page.maxh) { - self.checkContentSize(); - return self.noticeCursor(); - } - - self.doScrollTop(ny); - }; - - this.doScrollLeftBy = function(stp, relative) { - var nx = 0; - if (relative) { - nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x) - } else { - var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true); - nx = sx - stp; - } - if (self.bouncescroll) { - var haf = Math.round(self.view.w / 2); - if (nx < -haf) nx = -haf; - else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf); - } - self.cursorfreezed = false; - - var px = self.getScrollLeft(true); - if (nx < 0 && px <= 0) return self.noticeCursor(); - else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor(); - - self.doScrollLeft(nx); - }; - - this.doScrollTo = function(pos, relative) { - var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos; - if (ny < 0) ny = 0; - else if (ny > self.page.maxh) ny = self.page.maxh; - self.cursorfreezed = false; - self.doScrollTop(pos); - }; - - this.checkContentSize = function() { - var pg = self.getContentSize(); - if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg); - }; - - self.onscroll = function(e) { - if (self.rail.drag) return; - if (!self.cursorfreezed) { - self.synched('scroll', function() { - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x)); - self.noticeCursor(); - }); - } - }; - self.bind(self.docscroll, "scroll", self.onscroll); - - this.doZoomIn = function(e) { - if (self.zoomactive) return; - self.zoomactive = true; - - self.zoomrestore = { - style: {} - }; - var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight']; - var win = self.win[0].style; - for (var a in lst) { - var pp = lst[a]; - self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : ''; - } - - self.zoomrestore.style.width = self.win.css('width'); - self.zoomrestore.style.height = self.win.css('height'); - - self.zoomrestore.padding = { - w: self.win.outerWidth() - self.win.width(), - h: self.win.outerHeight() - self.win.height() - }; - - if (cap.isios4) { - self.zoomrestore.scrollTop = $(window).scrollTop(); - $(window).scrollTop(0); - } - - self.win.css({ - "position": (cap.isios4) ? "absolute" : "fixed", - "top": 0, - "left": 0, - "z-index": globalmaxzindex + 100, - "margin": "0px" - }); - var bkg = self.win.css("backgroundColor"); - if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff"); - self.rail.css({ - "z-index": globalmaxzindex + 101 - }); - self.zoom.css({ - "z-index": globalmaxzindex + 102 - }); - self.zoom.css('backgroundPosition', '0px -18px'); - self.resizeZoom(); - - if (self.onzoomin) self.onzoomin.call(self); - - return self.cancelEvent(e); - }; - - this.doZoomOut = function(e) { - if (!self.zoomactive) return; - self.zoomactive = false; - - self.win.css("margin", ""); - self.win.css(self.zoomrestore.style); - - if (cap.isios4) { - $(window).scrollTop(self.zoomrestore.scrollTop); - } - - self.rail.css({ - "z-index": self.zindex - }); - self.zoom.css({ - "z-index": self.zindex - }); - self.zoomrestore = false; - self.zoom.css('backgroundPosition', '0px 0px'); - self.onResize(); - - if (self.onzoomout) self.onzoomout.call(self); - - return self.cancelEvent(e); - }; - - this.doZoom = function(e) { - return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e); - }; - - this.resizeZoom = function() { - if (!self.zoomactive) return; - - var py = self.getScrollTop(); //preserve scrolling position - self.win.css({ - width: $(window).width() - self.zoomrestore.padding.w + "px", - height: $(window).height() - self.zoomrestore.padding.h + "px" - }); - self.onResize(); - - self.setScrollTop(Math.min(self.page.maxh, py)); - }; - - this.init(); - - $.nicescroll.push(this); - - }; - - // Inspired by the work of Kin Blas - // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js - - - var ScrollMomentumClass2D = function(nc) { - var self = this; - this.nc = nc; - - this.lastx = 0; - this.lasty = 0; - this.speedx = 0; - this.speedy = 0; - this.lasttime = 0; - this.steptime = 0; - this.snapx = false; - this.snapy = false; - this.demulx = 0; - this.demuly = 0; - - this.lastscrollx = -1; - this.lastscrolly = -1; - - this.chkx = 0; - this.chky = 0; - - this.timer = 0; - - this.time = function() { - return +new Date(); //beautifull hack - }; - - this.reset = function(px, py) { - self.stop(); - var now = self.time(); - self.steptime = 0; - self.lasttime = now; - self.speedx = 0; - self.speedy = 0; - self.lastx = px; - self.lasty = py; - self.lastscrollx = -1; - self.lastscrolly = -1; - }; - - this.update = function(px, py) { - var now = self.time(); - self.steptime = now - self.lasttime; - self.lasttime = now; - var dy = py - self.lasty; - var dx = px - self.lastx; - var sy = self.nc.getScrollTop(); - var sx = self.nc.getScrollLeft(); - var newy = sy + dy; - var newx = sx + dx; - self.snapx = (newx < 0) || (newx > self.nc.page.maxw); - self.snapy = (newy < 0) || (newy > self.nc.page.maxh); - self.speedx = dx; - self.speedy = dy; - self.lastx = px; - self.lasty = py; - }; - - this.stop = function() { - self.nc.unsynched("domomentum2d"); - if (self.timer) clearTimeout(self.timer); - self.timer = 0; - self.lastscrollx = -1; - self.lastscrolly = -1; - }; - - this.doSnapy = function(nx, ny) { - var snap = false; - - if (ny < 0) { - ny = 0; - snap = true; - } else if (ny > self.nc.page.maxh) { - ny = self.nc.page.maxh; - snap = true; - } - - if (nx < 0) { - nx = 0; - snap = true; - } else if (nx > self.nc.page.maxw) { - nx = self.nc.page.maxw; - snap = true; - } - - (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd(); - }; - - this.doMomentum = function(gp) { - var t = self.time(); - var l = (gp) ? t + gp : self.lasttime; - - var sl = self.nc.getScrollLeft(); - var st = self.nc.getScrollTop(); - - var pageh = self.nc.page.maxh; - var pagew = self.nc.page.maxw; - - self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0; - self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0; - - var chk = l && (t - l) <= 60; - - if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false; - - var sy = (self.speedy && chk) ? self.speedy : false; - var sx = (self.speedx && chk) ? self.speedx : false; - - if (sy || sx) { - var tm = Math.max(16, self.steptime); //timeout granularity - - if (tm > 50) { // do smooth - var xm = tm / 50; - self.speedx *= xm; - self.speedy *= xm; - tm = 50; - } - - self.demulxy = 0; - - self.lastscrollx = self.nc.getScrollLeft(); - self.chkx = self.lastscrollx; - self.lastscrolly = self.nc.getScrollTop(); - self.chky = self.lastscrolly; - - var nx = self.lastscrollx; - var ny = self.lastscrolly; - - var onscroll = function() { - var df = ((self.time() - t) > 600) ? 0.04 : 0.02; - - if (self.speedx) { - nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy))); - self.lastscrollx = nx; - if ((nx < 0) || (nx > pagew)) df = 0.10; - } - - if (self.speedy) { - ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy))); - self.lastscrolly = ny; - if ((ny < 0) || (ny > pageh)) df = 0.10; - } - - self.demulxy = Math.min(1, self.demulxy + df); - - self.nc.synched("domomentum2d", function() { - - if (self.speedx) { - var scx = self.nc.getScrollLeft(); - if (scx != self.chkx) self.stop(); - self.chkx = nx; - self.nc.setScrollLeft(nx); - } - - if (self.speedy) { - var scy = self.nc.getScrollTop(); - if (scy != self.chky) self.stop(); - self.chky = ny; - self.nc.setScrollTop(ny); - } - - if (!self.timer) { - self.nc.hideCursor(); - self.doSnapy(nx, ny); - } - - }); - - if (self.demulxy < 1) { - self.timer = setTimeout(onscroll, tm); - } else { - self.stop(); - self.nc.hideCursor(); - self.doSnapy(nx, ny); - } - }; - - onscroll(); - - } else { - self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop()); - } - - } - - }; - - - // override jQuery scrollTop - - var _scrollTop = jQuery.fn.scrollTop; // preserve original function - - jQuery.cssHooks["pageYOffset"] = { - get: function(elem, computed, extra) { - var nice = $.data(elem, '__nicescroll') || false; - return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem); - }, - set: function(elem, value) { - var nice = $.data(elem, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value); - return this; - } - }; - - /* - $.fx.step["scrollTop"] = function(fx){ - $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit ); - }; -*/ - - jQuery.fn.scrollTop = function(value) { - if (typeof value == "undefined") { - var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; - return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this); - } else { - return this.each(function() { - var nice = $.data(this, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value); - }); - } - }; - - // override jQuery scrollLeft - - var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function - - $.cssHooks.pageXOffset = { - get: function(elem, computed, extra) { - var nice = $.data(elem, '__nicescroll') || false; - return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem); - }, - set: function(elem, value) { - var nice = $.data(elem, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value); - return this; - } - }; - - /* - $.fx.step["scrollLeft"] = function(fx){ - $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit ); - }; -*/ - - jQuery.fn.scrollLeft = function(value) { - if (typeof value == "undefined") { - var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; - return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this); - } else { - return this.each(function() { - var nice = $.data(this, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value); - }); - } - }; - - var NiceScrollArray = function(doms) { - var self = this; - this.length = 0; - this.name = "nicescrollarray"; - - this.each = function(fn) { - for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++); - return self; - }; - - this.push = function(nice) { - self[self.length] = nice; - self.length++; - }; - - this.eq = function(idx) { - return self[idx]; - }; - - if (doms) { - for (var a = 0; a < doms.length; a++) { - var nice = $.data(doms[a], '__nicescroll') || false; - if (nice) { - this[this.length] = nice; - this.length++; - } - }; - } - - return this; - }; - - function mplex(el, lst, fn) { - for (var a = 0; a < lst.length; a++) fn(el, lst[a]); - }; - mplex( - NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'], - function(e, n) { - e[n] = function() { - var args = arguments; - return this.each(function() { - this[n].apply(this, args); - }); - }; - } - ); - - jQuery.fn.getNiceScroll = function(index) { - if (typeof index == "undefined") { - return new NiceScrollArray(this); - } else { - var nice = this[index] && $.data(this[index], '__nicescroll') || false; - return nice; - } - }; - - jQuery.extend(jQuery.expr[':'], { - nicescroll: function(a) { - return ($.data(a, '__nicescroll')) ? true : false; - } - }); - - $.fn.niceScroll = function(wrapper, opt) { - if (typeof opt == "undefined") { - if ((typeof wrapper == "object") && !("jquery" in wrapper)) { - opt = wrapper; - wrapper = false; - } - } - opt = $.extend({},opt); // cloning - var ret = new NiceScrollArray(); - if (typeof opt == "undefined") opt = {}; - - if (wrapper || false) { - opt.doc = $(wrapper); - opt.win = $(this); - } - var docundef = !("doc" in opt); - if (!docundef && !("win" in opt)) opt.win = $(this); - - this.each(function() { - var nice = $(this).data('__nicescroll') || false; - if (!nice) { - opt.doc = (docundef) ? $(this) : opt.doc; - nice = new NiceScrollClass(opt, $(this)); - $(this).data('__nicescroll', nice); - } - ret.push(nice); - }); - return (ret.length == 1) ? ret[0] : ret; - }; - - window.NiceScroll = { - getjQuery: function() { - return jQuery - } - }; - - if (!$.nicescroll) { - $.nicescroll = new NiceScrollArray(); - $.nicescroll.options = _globaloptions; - } - -}));
\ No newline at end of file diff --git a/vendor/assets/javascripts/pdflab.js b/vendor/assets/javascripts/pdf.js index 5d9c348ce35..91c43b12716 100644..100755 --- a/vendor/assets/javascripts/pdflab.js +++ b/vendor/assets/javascripts/pdf.js @@ -1,348 +1,4 @@ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define("PDFLab", [], factory); - else if(typeof exports === 'object') - exports["PDFLab"] = factory(); - else - root["PDFLab"] = factory(); -})(this, function() { -return /******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ var parentJsonpFunction = window["webpackJsonpPDFLab"]; -/******/ window["webpackJsonpPDFLab"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = [], result; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules); -/******/ while(resolves.length) -/******/ resolves.shift()(); -/******/ -/******/ }; -/******/ -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // objects to store loaded and loading chunks -/******/ var installedChunks = { -/******/ 1: 0, -/******/ 2: 0 -/******/ }; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; -/******/ -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ // This file contains only the entry chunk. -/******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ if(installedChunks[chunkId] === 0) -/******/ return Promise.resolve(); -/******/ -/******/ // an Promise means "currently loading". -/******/ if(installedChunks[chunkId]) { -/******/ return installedChunks[chunkId][2]; -/******/ } -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ script.type = 'text/javascript'; -/******/ script.charset = 'utf-8'; -/******/ script.async = true; -/******/ script.timeout = 120000; -/******/ -/******/ if (__webpack_require__.nc) { -/******/ script.setAttribute("nonce", __webpack_require__.nc); -/******/ } -/******/ script.src = __webpack_require__.p + "" + chunkId + ".js"; -/******/ var timeout = setTimeout(onScriptComplete, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete() { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ installedChunks[chunkId][2] = promise; -/******/ -/******/ head.appendChild(script); -/******/ return promise; -/******/ }; -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 23); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports) { - -// shim for using process in browser -var process = module.exports = {}; - -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. - -var cachedSetTimeout; -var cachedClearTimeout; - -function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); -} -function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); -} -(function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } -} ()) -function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } - - -} -function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } - - - -} -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; - -function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } -} - -function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); -} - -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - - -/***/ }), -/* 1 */, -/* 2 */ -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(process) {/* Copyright 2017 Mozilla Foundation +/* Copyright 2017 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -358,7 +14,7 @@ process.umask = function() { return 0; }; */ (function webpackUniversalModuleDefinition(root, factory) { - if(true) + if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("pdfjs-dist/build/pdf", [], factory); @@ -2478,11 +2134,11 @@ var useRequireEnsure = false; if (typeof __pdfjsdev_webpack__ === 'undefined') { if (typeof window === 'undefined') { isWorkerDisabled = true; - if (false) { + if (typeof require.ensure === 'undefined') { require.ensure = require('node-ensure'); } useRequireEnsure = true; - } else if (true) { + } else if (typeof require !== 'undefined' && typeof require.ensure === 'function') { useRequireEnsure = true; } if (typeof requirejs !== 'undefined' && requirejs.toUrl) { @@ -2490,10 +2146,10 @@ if (typeof __pdfjsdev_webpack__ === 'undefined') { } var dynamicLoaderSupported = typeof requirejs !== 'undefined' && requirejs.load; fakeWorkerFilesLoader = useRequireEnsure ? function (callback) { - __webpack_require__.e/* require.ensure */(0).then((function () { - var worker = __webpack_require__(1); + require.ensure([], function () { + var worker = require('./pdf.worker.js'); callback(worker.WorkerMessageHandler); - }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + }); } : dynamicLoaderSupported ? function (callback) { requirejs(['pdfjs-dist/build/pdf.worker'], function (worker) { callback(worker.WorkerMessageHandler); @@ -9706,2779 +9362,4 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) { /***/ }) /******/ ]); -}); -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0))) - -/***/ }), -/* 3 */ -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(Buffer) {/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ -// css base code, injected by the css-loader -module.exports = function(useSourceMap) { - var list = []; - - // return the list of modules as css string - list.toString = function toString() { - return this.map(function (item) { - var content = cssWithMappingToString(item, useSourceMap); - if(item[2]) { - return "@media " + item[2] + "{" + content + "}"; - } else { - return content; - } - }).join(""); - }; - - // import a list of modules into the list - list.i = function(modules, mediaQuery) { - if(typeof modules === "string") - modules = [[null, modules, ""]]; - var alreadyImportedModules = {}; - for(var i = 0; i < this.length; i++) { - var id = this[i][0]; - if(typeof id === "number") - alreadyImportedModules[id] = true; - } - for(i = 0; i < modules.length; i++) { - var item = modules[i]; - // skip already imported module - // this implementation is not 100% perfect for weird media query combinations - // when a module is imported multiple times with different media queries. - // I hope this will never occur (Hey this way we have smaller bundles) - if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { - if(mediaQuery && !item[2]) { - item[2] = mediaQuery; - } else if(mediaQuery) { - item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; - } - list.push(item); - } - } - }; - return list; -}; - -function cssWithMappingToString(item, useSourceMap) { - var content = item[1] || ''; - var cssMapping = item[3]; - if (!cssMapping) { - return content; - } - - if (useSourceMap) { - var sourceMapping = toComment(cssMapping); - var sourceURLs = cssMapping.sources.map(function (source) { - return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' - }); - - return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); - } - - return [content].join('\n'); -} - -// Adapted from convert-source-map (MIT) -function toComment(sourceMap) { - var base64 = new Buffer(JSON.stringify(sourceMap)).toString('base64'); - var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; - - return '/*# ' + data + ' */'; -} - -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer)) - -/***/ }), -/* 4 */ -/***/ (function(module, exports) { - -// this module is a runtime utility for cleaner component module output and will -// be included in the final webpack user bundle - -module.exports = function normalizeComponent ( - rawScriptExports, - compiledTemplate, - scopeId, - cssModules -) { - var esModule - var scriptExports = rawScriptExports = rawScriptExports || {} - - // ES6 modules interop - var type = typeof rawScriptExports.default - if (type === 'object' || type === 'function') { - esModule = rawScriptExports - scriptExports = rawScriptExports.default - } - - // Vue.extend constructor export interop - var options = typeof scriptExports === 'function' - ? scriptExports.options - : scriptExports - - // render functions - if (compiledTemplate) { - options.render = compiledTemplate.render - options.staticRenderFns = compiledTemplate.staticRenderFns - } - - // scopedId - if (scopeId) { - options._scopeId = scopeId - } - - // inject cssModules - if (cssModules) { - var computed = Object.create(options.computed || null) - Object.keys(cssModules).forEach(function (key) { - var module = cssModules[key] - computed[key] = function () { return module } - }) - options.computed = computed - } - - return { - esModule: esModule, - exports: scriptExports, - options: options - } -} - - -/***/ }), -/* 5 */ -/***/ (function(module, exports, __webpack_require__) { - -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - Modified by Evan You @yyx990803 -*/ - -var hasDocument = typeof document !== 'undefined' - -if (typeof DEBUG !== 'undefined' && DEBUG) { - if (!hasDocument) { - throw new Error( - 'vue-style-loader cannot be used in a non-browser environment. ' + - "Use { target: 'node' } in your Webpack config to indicate a server-rendering environment." - ) } -} - -var listToStyles = __webpack_require__(21) - -/* -type StyleObject = { - id: number; - parts: Array<StyleObjectPart> -} - -type StyleObjectPart = { - css: string; - media: string; - sourceMap: ?string -} -*/ - -var stylesInDom = {/* - [id: number]: { - id: number, - refs: number, - parts: Array<(obj?: StyleObjectPart) => void> - } -*/} - -var head = hasDocument && (document.head || document.getElementsByTagName('head')[0]) -var singletonElement = null -var singletonCounter = 0 -var isProduction = false -var noop = function () {} - -// Force single-tag solution on IE6-9, which has a hard limit on the # of <style> -// tags it will allow on a page -var isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\b/.test(navigator.userAgent.toLowerCase()) - -module.exports = function (parentId, list, _isProduction) { - isProduction = _isProduction - - var styles = listToStyles(parentId, list) - addStylesToDom(styles) - - return function update (newList) { - var mayRemove = [] - for (var i = 0; i < styles.length; i++) { - var item = styles[i] - var domStyle = stylesInDom[item.id] - domStyle.refs-- - mayRemove.push(domStyle) - } - if (newList) { - styles = listToStyles(parentId, newList) - addStylesToDom(styles) - } else { - styles = [] - } - for (var i = 0; i < mayRemove.length; i++) { - var domStyle = mayRemove[i] - if (domStyle.refs === 0) { - for (var j = 0; j < domStyle.parts.length; j++) { - domStyle.parts[j]() - } - delete stylesInDom[domStyle.id] - } - } - } -} - -function addStylesToDom (styles /* Array<StyleObject> */) { - for (var i = 0; i < styles.length; i++) { - var item = styles[i] - var domStyle = stylesInDom[item.id] - if (domStyle) { - domStyle.refs++ - for (var j = 0; j < domStyle.parts.length; j++) { - domStyle.parts[j](item.parts[j]) - } - for (; j < item.parts.length; j++) { - domStyle.parts.push(addStyle(item.parts[j])) - } - if (domStyle.parts.length > item.parts.length) { - domStyle.parts.length = item.parts.length - } - } else { - var parts = [] - for (var j = 0; j < item.parts.length; j++) { - parts.push(addStyle(item.parts[j])) - } - stylesInDom[item.id] = { id: item.id, refs: 1, parts: parts } - } - } -} - -function createStyleElement () { - var styleElement = document.createElement('style') - styleElement.type = 'text/css' - head.appendChild(styleElement) - return styleElement -} - -function addStyle (obj /* StyleObjectPart */) { - var update, remove - var styleElement = document.querySelector('style[data-vue-ssr-id~="' + obj.id + '"]') - - if (styleElement) { - if (isProduction) { - // has SSR styles and in production mode. - // simply do nothing. - return noop - } else { - // has SSR styles but in dev mode. - // for some reason Chrome can't handle source map in server-rendered - // style tags - source maps in <style> only works if the style tag is - // created and inserted dynamically. So we remove the server rendered - // styles and inject new ones. - styleElement.parentNode.removeChild(styleElement) - } - } - - if (isOldIE) { - // use singleton mode for IE9. - var styleIndex = singletonCounter++ - styleElement = singletonElement || (singletonElement = createStyleElement()) - update = applyToSingletonTag.bind(null, styleElement, styleIndex, false) - remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true) - } else { - // use multi-style-tag mode in all other cases - styleElement = createStyleElement() - update = applyToTag.bind(null, styleElement) - remove = function () { - styleElement.parentNode.removeChild(styleElement) - } - } - - update(obj) - - return function updateStyle (newObj /* StyleObjectPart */) { - if (newObj) { - if (newObj.css === obj.css && - newObj.media === obj.media && - newObj.sourceMap === obj.sourceMap) { - return - } - update(obj = newObj) - } else { - remove() - } - } -} - -var replaceText = (function () { - var textStore = [] - - return function (index, replacement) { - textStore[index] = replacement - return textStore.filter(Boolean).join('\n') - } -})() - -function applyToSingletonTag (styleElement, index, remove, obj) { - var css = remove ? '' : obj.css - - if (styleElement.styleSheet) { - styleElement.styleSheet.cssText = replaceText(index, css) - } else { - var cssNode = document.createTextNode(css) - var childNodes = styleElement.childNodes - if (childNodes[index]) styleElement.removeChild(childNodes[index]) - if (childNodes.length) { - styleElement.insertBefore(cssNode, childNodes[index]) - } else { - styleElement.appendChild(cssNode) - } - } -} - -function applyToTag (styleElement, obj) { - var css = obj.css - var media = obj.media - var sourceMap = obj.sourceMap - - if (media) { - styleElement.setAttribute('media', media) - } - - if (sourceMap) { - // https://developer.chrome.com/devtools/docs/javascript-debugging - // this makes source maps inside style tags work properly in Chrome - css += '\n/*# sourceURL=' + sourceMap.sources[0] + ' */' - // http://stackoverflow.com/a/26603875 - css += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + ' */' - } - - if (styleElement.styleSheet) { - styleElement.styleSheet.cssText = css - } else { - while (styleElement.firstChild) { - styleElement.removeChild(styleElement.firstChild) - } - styleElement.appendChild(document.createTextNode(css)) - } -} - - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - - -/* styles */ -__webpack_require__(19) - -var Component = __webpack_require__(4)( - /* script */ - __webpack_require__(7), - /* template */ - __webpack_require__(17), - /* scopeId */ - null, - /* cssModules */ - null -) - -module.exports = Component.exports - - -/***/ }), -/* 7 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _pdfjsDist = __webpack_require__(2); - -var _pdfjsDist2 = _interopRequireDefault(_pdfjsDist); - -var _index = __webpack_require__(16); - -var _index2 = _interopRequireDefault(_index); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// -// -// -// -// -// -// -// -// -// - -exports.default = { - props: { - pdf: { - type: [String, Uint8Array], - required: true - } - }, - data: function data() { - return { - loading: false, - pages: [] - }; - }, - - components: { page: _index2.default }, - watch: { pdf: 'load' }, - computed: { - document: function document() { - return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf }; - }, - hasPDF: function hasPDF() { - return this.pdf && this.pdf.length > 0; - } - }, - methods: { - load: function load() { - var _this = this; - - this.pages = []; - return _pdfjsDist2.default.getDocument(this.document).then(this.renderPages).then(function () { - return _this.$emit('pdflabload'); - }).catch(function (error) { - return _this.$emit('pdflaberror', error); - }).then(function () { - _this.loading = false; - }); - }, - renderPages: function renderPages(pdf) { - var _this2 = this; - - var pagePromises = []; - this.loading = true; - for (var num = 1; num <= pdf.numPages; num += 1) { - pagePromises.push(pdf.getPage(num).then(function (p) { - return _this2.pages.push(p); - })); - } - return Promise.all(pagePromises); - } - }, - mounted: function mounted() { - if (this.hasPDF) this.load(); - } -}; - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -// -// -// -// -// -// -// - -exports.default = { - props: { - page: { - type: Object, - required: true - }, - number: { - type: Number, - required: true - } - }, - data: function data() { - return { - scale: 4, - rendering: false - }; - }, - - computed: { - viewport: function viewport() { - return this.page.getViewport(this.scale); - }, - context: function context() { - return this.$refs.canvas.getContext('2d'); - }, - renderContext: function renderContext() { - return { - canvasContext: this.context, - viewport: this.viewport - }; - } - }, - mounted: function mounted() { - var _this = this; - - this.$refs.canvas.height = this.viewport.height; - this.$refs.canvas.width = this.viewport.width; - this.rendering = true; - this.page.render(this.renderContext).then(function () { - _this.rendering = false; - }); - } -}; - -/***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.byteLength = byteLength -exports.toByteArray = toByteArray -exports.fromByteArray = fromByteArray - -var lookup = [] -var revLookup = [] -var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array - -var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -for (var i = 0, len = code.length; i < len; ++i) { - lookup[i] = code[i] - revLookup[code.charCodeAt(i)] = i -} - -revLookup['-'.charCodeAt(0)] = 62 -revLookup['_'.charCodeAt(0)] = 63 - -function placeHoldersCount (b64) { - var len = b64.length - if (len % 4 > 0) { - throw new Error('Invalid string. Length must be a multiple of 4') - } - - // the number of equal signs (place holders) - // if there are two placeholders, than the two characters before it - // represent one byte - // if there is only one, then the three characters before it represent 2 bytes - // this is just a cheap hack to not do indexOf twice - return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 -} - -function byteLength (b64) { - // base64 is 4/3 + up to two characters of the original data - return b64.length * 3 / 4 - placeHoldersCount(b64) -} - -function toByteArray (b64) { - var i, j, l, tmp, placeHolders, arr - var len = b64.length - placeHolders = placeHoldersCount(b64) - - arr = new Arr(len * 3 / 4 - placeHolders) - - // if there are placeholders, only get up to the last complete 4 chars - l = placeHolders > 0 ? len - 4 : len - - var L = 0 - - for (i = 0, j = 0; i < l; i += 4, j += 3) { - tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] - arr[L++] = (tmp >> 16) & 0xFF - arr[L++] = (tmp >> 8) & 0xFF - arr[L++] = tmp & 0xFF - } - - if (placeHolders === 2) { - tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) - arr[L++] = tmp & 0xFF - } else if (placeHolders === 1) { - tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) - arr[L++] = (tmp >> 8) & 0xFF - arr[L++] = tmp & 0xFF - } - - return arr -} - -function tripletToBase64 (num) { - return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] -} - -function encodeChunk (uint8, start, end) { - var tmp - var output = [] - for (var i = start; i < end; i += 3) { - tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) - output.push(tripletToBase64(tmp)) - } - return output.join('') -} - -function fromByteArray (uint8) { - var tmp - var len = uint8.length - var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes - var output = '' - var parts = [] - var maxChunkLength = 16383 // must be multiple of 3 - - // go through the array every three bytes, we'll deal with trailing stuff later - for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { - parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) - } - - // pad the end with zeros, but make sure to not forget the extra bytes - if (extraBytes === 1) { - tmp = uint8[len - 1] - output += lookup[tmp >> 2] - output += lookup[(tmp << 4) & 0x3F] - output += '==' - } else if (extraBytes === 2) { - tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) - output += lookup[tmp >> 10] - output += lookup[(tmp >> 4) & 0x3F] - output += lookup[(tmp << 2) & 0x3F] - output += '=' - } - - parts.push(output) - - return parts.join('') -} - - -/***/ }), -/* 10 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* WEBPACK VAR INJECTION */(function(global) {/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org> - * @license MIT - */ -/* eslint-disable no-proto */ - - - -var base64 = __webpack_require__(9) -var ieee754 = __webpack_require__(13) -var isArray = __webpack_require__(14) - -exports.Buffer = Buffer -exports.SlowBuffer = SlowBuffer -exports.INSPECT_MAX_BYTES = 50 - -/** - * If `Buffer.TYPED_ARRAY_SUPPORT`: - * === true Use Uint8Array implementation (fastest) - * === false Use Object implementation (most compatible, even IE6) - * - * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, - * Opera 11.6+, iOS 4.2+. - * - * Due to various browser bugs, sometimes the Object implementation will be used even - * when the browser supports typed arrays. - * - * Note: - * - * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances, - * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. - * - * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. - * - * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of - * incorrect length in some situations. - - * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they - * get the Object implementation, which is slower but behaves correctly. - */ -Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined - ? global.TYPED_ARRAY_SUPPORT - : typedArraySupport() - -/* - * Export kMaxLength after typed array support is determined. - */ -exports.kMaxLength = kMaxLength() - -function typedArraySupport () { - try { - var arr = new Uint8Array(1) - arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} - return arr.foo() === 42 && // typed array instances can be augmented - typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` - arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` - } catch (e) { - return false - } -} - -function kMaxLength () { - return Buffer.TYPED_ARRAY_SUPPORT - ? 0x7fffffff - : 0x3fffffff -} - -function createBuffer (that, length) { - if (kMaxLength() < length) { - throw new RangeError('Invalid typed array length') - } - if (Buffer.TYPED_ARRAY_SUPPORT) { - // Return an augmented `Uint8Array` instance, for best performance - that = new Uint8Array(length) - that.__proto__ = Buffer.prototype - } else { - // Fallback: Return an object instance of the Buffer class - if (that === null) { - that = new Buffer(length) - } - that.length = length - } - - return that -} - -/** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. - */ - -function Buffer (arg, encodingOrOffset, length) { - if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) { - return new Buffer(arg, encodingOrOffset, length) - } - - // Common case. - if (typeof arg === 'number') { - if (typeof encodingOrOffset === 'string') { - throw new Error( - 'If encoding is specified then the first argument must be a string' - ) - } - return allocUnsafe(this, arg) - } - return from(this, arg, encodingOrOffset, length) -} - -Buffer.poolSize = 8192 // not used by this implementation - -// TODO: Legacy, not needed anymore. Remove in next major version. -Buffer._augment = function (arr) { - arr.__proto__ = Buffer.prototype - return arr -} - -function from (that, value, encodingOrOffset, length) { - if (typeof value === 'number') { - throw new TypeError('"value" argument must not be a number') - } - - if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { - return fromArrayBuffer(that, value, encodingOrOffset, length) - } - - if (typeof value === 'string') { - return fromString(that, value, encodingOrOffset) - } - - return fromObject(that, value) -} - -/** - * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError - * if value is a number. - * Buffer.from(str[, encoding]) - * Buffer.from(array) - * Buffer.from(buffer) - * Buffer.from(arrayBuffer[, byteOffset[, length]]) - **/ -Buffer.from = function (value, encodingOrOffset, length) { - return from(null, value, encodingOrOffset, length) -} - -if (Buffer.TYPED_ARRAY_SUPPORT) { - Buffer.prototype.__proto__ = Uint8Array.prototype - Buffer.__proto__ = Uint8Array - if (typeof Symbol !== 'undefined' && Symbol.species && - Buffer[Symbol.species] === Buffer) { - // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 - Object.defineProperty(Buffer, Symbol.species, { - value: null, - configurable: true - }) - } -} - -function assertSize (size) { - if (typeof size !== 'number') { - throw new TypeError('"size" argument must be a number') - } else if (size < 0) { - throw new RangeError('"size" argument must not be negative') - } -} - -function alloc (that, size, fill, encoding) { - assertSize(size) - if (size <= 0) { - return createBuffer(that, size) - } - if (fill !== undefined) { - // Only pay attention to encoding if it's a string. This - // prevents accidentally sending in a number that would - // be interpretted as a start offset. - return typeof encoding === 'string' - ? createBuffer(that, size).fill(fill, encoding) - : createBuffer(that, size).fill(fill) - } - return createBuffer(that, size) -} - -/** - * Creates a new filled Buffer instance. - * alloc(size[, fill[, encoding]]) - **/ -Buffer.alloc = function (size, fill, encoding) { - return alloc(null, size, fill, encoding) -} - -function allocUnsafe (that, size) { - assertSize(size) - that = createBuffer(that, size < 0 ? 0 : checked(size) | 0) - if (!Buffer.TYPED_ARRAY_SUPPORT) { - for (var i = 0; i < size; ++i) { - that[i] = 0 - } - } - return that -} - -/** - * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. - * */ -Buffer.allocUnsafe = function (size) { - return allocUnsafe(null, size) -} -/** - * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. - */ -Buffer.allocUnsafeSlow = function (size) { - return allocUnsafe(null, size) -} - -function fromString (that, string, encoding) { - if (typeof encoding !== 'string' || encoding === '') { - encoding = 'utf8' - } - - if (!Buffer.isEncoding(encoding)) { - throw new TypeError('"encoding" must be a valid string encoding') - } - - var length = byteLength(string, encoding) | 0 - that = createBuffer(that, length) - - var actual = that.write(string, encoding) - - if (actual !== length) { - // Writing a hex string, for example, that contains invalid characters will - // cause everything after the first invalid character to be ignored. (e.g. - // 'abxxcd' will be treated as 'ab') - that = that.slice(0, actual) - } - - return that -} - -function fromArrayLike (that, array) { - var length = array.length < 0 ? 0 : checked(array.length) | 0 - that = createBuffer(that, length) - for (var i = 0; i < length; i += 1) { - that[i] = array[i] & 255 - } - return that -} - -function fromArrayBuffer (that, array, byteOffset, length) { - array.byteLength // this throws if `array` is not a valid ArrayBuffer - - if (byteOffset < 0 || array.byteLength < byteOffset) { - throw new RangeError('\'offset\' is out of bounds') - } - - if (array.byteLength < byteOffset + (length || 0)) { - throw new RangeError('\'length\' is out of bounds') - } - - if (byteOffset === undefined && length === undefined) { - array = new Uint8Array(array) - } else if (length === undefined) { - array = new Uint8Array(array, byteOffset) - } else { - array = new Uint8Array(array, byteOffset, length) - } - - if (Buffer.TYPED_ARRAY_SUPPORT) { - // Return an augmented `Uint8Array` instance, for best performance - that = array - that.__proto__ = Buffer.prototype - } else { - // Fallback: Return an object instance of the Buffer class - that = fromArrayLike(that, array) - } - return that -} - -function fromObject (that, obj) { - if (Buffer.isBuffer(obj)) { - var len = checked(obj.length) | 0 - that = createBuffer(that, len) - - if (that.length === 0) { - return that - } - - obj.copy(that, 0, 0, len) - return that - } - - if (obj) { - if ((typeof ArrayBuffer !== 'undefined' && - obj.buffer instanceof ArrayBuffer) || 'length' in obj) { - if (typeof obj.length !== 'number' || isnan(obj.length)) { - return createBuffer(that, 0) - } - return fromArrayLike(that, obj) - } - - if (obj.type === 'Buffer' && isArray(obj.data)) { - return fromArrayLike(that, obj.data) - } - } - - throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') -} - -function checked (length) { - // Note: cannot use `length < kMaxLength()` here because that fails when - // length is NaN (which is otherwise coerced to zero.) - if (length >= kMaxLength()) { - throw new RangeError('Attempt to allocate Buffer larger than maximum ' + - 'size: 0x' + kMaxLength().toString(16) + ' bytes') - } - return length | 0 -} - -function SlowBuffer (length) { - if (+length != length) { // eslint-disable-line eqeqeq - length = 0 - } - return Buffer.alloc(+length) -} - -Buffer.isBuffer = function isBuffer (b) { - return !!(b != null && b._isBuffer) -} - -Buffer.compare = function compare (a, b) { - if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { - throw new TypeError('Arguments must be Buffers') - } - - if (a === b) return 0 - - var x = a.length - var y = b.length - - for (var i = 0, len = Math.min(x, y); i < len; ++i) { - if (a[i] !== b[i]) { - x = a[i] - y = b[i] - break - } - } - - if (x < y) return -1 - if (y < x) return 1 - return 0 -} - -Buffer.isEncoding = function isEncoding (encoding) { - switch (String(encoding).toLowerCase()) { - case 'hex': - case 'utf8': - case 'utf-8': - case 'ascii': - case 'latin1': - case 'binary': - case 'base64': - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return true - default: - return false - } -} - -Buffer.concat = function concat (list, length) { - if (!isArray(list)) { - throw new TypeError('"list" argument must be an Array of Buffers') - } - - if (list.length === 0) { - return Buffer.alloc(0) - } - - var i - if (length === undefined) { - length = 0 - for (i = 0; i < list.length; ++i) { - length += list[i].length - } - } - - var buffer = Buffer.allocUnsafe(length) - var pos = 0 - for (i = 0; i < list.length; ++i) { - var buf = list[i] - if (!Buffer.isBuffer(buf)) { - throw new TypeError('"list" argument must be an Array of Buffers') - } - buf.copy(buffer, pos) - pos += buf.length - } - return buffer -} - -function byteLength (string, encoding) { - if (Buffer.isBuffer(string)) { - return string.length - } - if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' && - (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) { - return string.byteLength - } - if (typeof string !== 'string') { - string = '' + string - } - - var len = string.length - if (len === 0) return 0 - - // Use a for loop to avoid recursion - var loweredCase = false - for (;;) { - switch (encoding) { - case 'ascii': - case 'latin1': - case 'binary': - return len - case 'utf8': - case 'utf-8': - case undefined: - return utf8ToBytes(string).length - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return len * 2 - case 'hex': - return len >>> 1 - case 'base64': - return base64ToBytes(string).length - default: - if (loweredCase) return utf8ToBytes(string).length // assume utf8 - encoding = ('' + encoding).toLowerCase() - loweredCase = true - } - } -} -Buffer.byteLength = byteLength - -function slowToString (encoding, start, end) { - var loweredCase = false - - // No need to verify that "this.length <= MAX_UINT32" since it's a read-only - // property of a typed array. - - // This behaves neither like String nor Uint8Array in that we set start/end - // to their upper/lower bounds if the value passed is out of range. - // undefined is handled specially as per ECMA-262 6th Edition, - // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. - if (start === undefined || start < 0) { - start = 0 - } - // Return early if start > this.length. Done here to prevent potential uint32 - // coercion fail below. - if (start > this.length) { - return '' - } - - if (end === undefined || end > this.length) { - end = this.length - } - - if (end <= 0) { - return '' - } - - // Force coersion to uint32. This will also coerce falsey/NaN values to 0. - end >>>= 0 - start >>>= 0 - - if (end <= start) { - return '' - } - - if (!encoding) encoding = 'utf8' - - while (true) { - switch (encoding) { - case 'hex': - return hexSlice(this, start, end) - - case 'utf8': - case 'utf-8': - return utf8Slice(this, start, end) - - case 'ascii': - return asciiSlice(this, start, end) - - case 'latin1': - case 'binary': - return latin1Slice(this, start, end) - - case 'base64': - return base64Slice(this, start, end) - - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return utf16leSlice(this, start, end) - - default: - if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) - encoding = (encoding + '').toLowerCase() - loweredCase = true - } - } -} - -// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect -// Buffer instances. -Buffer.prototype._isBuffer = true - -function swap (b, n, m) { - var i = b[n] - b[n] = b[m] - b[m] = i -} - -Buffer.prototype.swap16 = function swap16 () { - var len = this.length - if (len % 2 !== 0) { - throw new RangeError('Buffer size must be a multiple of 16-bits') - } - for (var i = 0; i < len; i += 2) { - swap(this, i, i + 1) - } - return this -} - -Buffer.prototype.swap32 = function swap32 () { - var len = this.length - if (len % 4 !== 0) { - throw new RangeError('Buffer size must be a multiple of 32-bits') - } - for (var i = 0; i < len; i += 4) { - swap(this, i, i + 3) - swap(this, i + 1, i + 2) - } - return this -} - -Buffer.prototype.swap64 = function swap64 () { - var len = this.length - if (len % 8 !== 0) { - throw new RangeError('Buffer size must be a multiple of 64-bits') - } - for (var i = 0; i < len; i += 8) { - swap(this, i, i + 7) - swap(this, i + 1, i + 6) - swap(this, i + 2, i + 5) - swap(this, i + 3, i + 4) - } - return this -} - -Buffer.prototype.toString = function toString () { - var length = this.length | 0 - if (length === 0) return '' - if (arguments.length === 0) return utf8Slice(this, 0, length) - return slowToString.apply(this, arguments) -} - -Buffer.prototype.equals = function equals (b) { - if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') - if (this === b) return true - return Buffer.compare(this, b) === 0 -} - -Buffer.prototype.inspect = function inspect () { - var str = '' - var max = exports.INSPECT_MAX_BYTES - if (this.length > 0) { - str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') - if (this.length > max) str += ' ... ' - } - return '<Buffer ' + str + '>' -} - -Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { - if (!Buffer.isBuffer(target)) { - throw new TypeError('Argument must be a Buffer') - } - - if (start === undefined) { - start = 0 - } - if (end === undefined) { - end = target ? target.length : 0 - } - if (thisStart === undefined) { - thisStart = 0 - } - if (thisEnd === undefined) { - thisEnd = this.length - } - - if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { - throw new RangeError('out of range index') - } - - if (thisStart >= thisEnd && start >= end) { - return 0 - } - if (thisStart >= thisEnd) { - return -1 - } - if (start >= end) { - return 1 - } - - start >>>= 0 - end >>>= 0 - thisStart >>>= 0 - thisEnd >>>= 0 - - if (this === target) return 0 - - var x = thisEnd - thisStart - var y = end - start - var len = Math.min(x, y) - - var thisCopy = this.slice(thisStart, thisEnd) - var targetCopy = target.slice(start, end) - - for (var i = 0; i < len; ++i) { - if (thisCopy[i] !== targetCopy[i]) { - x = thisCopy[i] - y = targetCopy[i] - break - } - } - - if (x < y) return -1 - if (y < x) return 1 - return 0 -} - -// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, -// OR the last index of `val` in `buffer` at offset <= `byteOffset`. -// -// Arguments: -// - buffer - a Buffer to search -// - val - a string, Buffer, or number -// - byteOffset - an index into `buffer`; will be clamped to an int32 -// - encoding - an optional encoding, relevant is val is a string -// - dir - true for indexOf, false for lastIndexOf -function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { - // Empty buffer means no match - if (buffer.length === 0) return -1 - - // Normalize byteOffset - if (typeof byteOffset === 'string') { - encoding = byteOffset - byteOffset = 0 - } else if (byteOffset > 0x7fffffff) { - byteOffset = 0x7fffffff - } else if (byteOffset < -0x80000000) { - byteOffset = -0x80000000 - } - byteOffset = +byteOffset // Coerce to Number. - if (isNaN(byteOffset)) { - // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer - byteOffset = dir ? 0 : (buffer.length - 1) - } - - // Normalize byteOffset: negative offsets start from the end of the buffer - if (byteOffset < 0) byteOffset = buffer.length + byteOffset - if (byteOffset >= buffer.length) { - if (dir) return -1 - else byteOffset = buffer.length - 1 - } else if (byteOffset < 0) { - if (dir) byteOffset = 0 - else return -1 - } - - // Normalize val - if (typeof val === 'string') { - val = Buffer.from(val, encoding) - } - - // Finally, search either indexOf (if dir is true) or lastIndexOf - if (Buffer.isBuffer(val)) { - // Special case: looking for empty string/buffer always fails - if (val.length === 0) { - return -1 - } - return arrayIndexOf(buffer, val, byteOffset, encoding, dir) - } else if (typeof val === 'number') { - val = val & 0xFF // Search for a byte value [0-255] - if (Buffer.TYPED_ARRAY_SUPPORT && - typeof Uint8Array.prototype.indexOf === 'function') { - if (dir) { - return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) - } else { - return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) - } - } - return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) - } - - throw new TypeError('val must be string, number or Buffer') -} - -function arrayIndexOf (arr, val, byteOffset, encoding, dir) { - var indexSize = 1 - var arrLength = arr.length - var valLength = val.length - - if (encoding !== undefined) { - encoding = String(encoding).toLowerCase() - if (encoding === 'ucs2' || encoding === 'ucs-2' || - encoding === 'utf16le' || encoding === 'utf-16le') { - if (arr.length < 2 || val.length < 2) { - return -1 - } - indexSize = 2 - arrLength /= 2 - valLength /= 2 - byteOffset /= 2 - } - } - - function read (buf, i) { - if (indexSize === 1) { - return buf[i] - } else { - return buf.readUInt16BE(i * indexSize) - } - } - - var i - if (dir) { - var foundIndex = -1 - for (i = byteOffset; i < arrLength; i++) { - if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { - if (foundIndex === -1) foundIndex = i - if (i - foundIndex + 1 === valLength) return foundIndex * indexSize - } else { - if (foundIndex !== -1) i -= i - foundIndex - foundIndex = -1 - } - } - } else { - if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength - for (i = byteOffset; i >= 0; i--) { - var found = true - for (var j = 0; j < valLength; j++) { - if (read(arr, i + j) !== read(val, j)) { - found = false - break - } - } - if (found) return i - } - } - - return -1 -} - -Buffer.prototype.includes = function includes (val, byteOffset, encoding) { - return this.indexOf(val, byteOffset, encoding) !== -1 -} - -Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { - return bidirectionalIndexOf(this, val, byteOffset, encoding, true) -} - -Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { - return bidirectionalIndexOf(this, val, byteOffset, encoding, false) -} - -function hexWrite (buf, string, offset, length) { - offset = Number(offset) || 0 - var remaining = buf.length - offset - if (!length) { - length = remaining - } else { - length = Number(length) - if (length > remaining) { - length = remaining - } - } - - // must be an even number of digits - var strLen = string.length - if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') - - if (length > strLen / 2) { - length = strLen / 2 - } - for (var i = 0; i < length; ++i) { - var parsed = parseInt(string.substr(i * 2, 2), 16) - if (isNaN(parsed)) return i - buf[offset + i] = parsed - } - return i -} - -function utf8Write (buf, string, offset, length) { - return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) -} - -function asciiWrite (buf, string, offset, length) { - return blitBuffer(asciiToBytes(string), buf, offset, length) -} - -function latin1Write (buf, string, offset, length) { - return asciiWrite(buf, string, offset, length) -} - -function base64Write (buf, string, offset, length) { - return blitBuffer(base64ToBytes(string), buf, offset, length) -} - -function ucs2Write (buf, string, offset, length) { - return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) -} - -Buffer.prototype.write = function write (string, offset, length, encoding) { - // Buffer#write(string) - if (offset === undefined) { - encoding = 'utf8' - length = this.length - offset = 0 - // Buffer#write(string, encoding) - } else if (length === undefined && typeof offset === 'string') { - encoding = offset - length = this.length - offset = 0 - // Buffer#write(string, offset[, length][, encoding]) - } else if (isFinite(offset)) { - offset = offset | 0 - if (isFinite(length)) { - length = length | 0 - if (encoding === undefined) encoding = 'utf8' - } else { - encoding = length - length = undefined - } - // legacy write(string, encoding, offset, length) - remove in v0.13 - } else { - throw new Error( - 'Buffer.write(string, encoding, offset[, length]) is no longer supported' - ) - } - - var remaining = this.length - offset - if (length === undefined || length > remaining) length = remaining - - if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { - throw new RangeError('Attempt to write outside buffer bounds') - } - - if (!encoding) encoding = 'utf8' - - var loweredCase = false - for (;;) { - switch (encoding) { - case 'hex': - return hexWrite(this, string, offset, length) - - case 'utf8': - case 'utf-8': - return utf8Write(this, string, offset, length) - - case 'ascii': - return asciiWrite(this, string, offset, length) - - case 'latin1': - case 'binary': - return latin1Write(this, string, offset, length) - - case 'base64': - // Warning: maxLength not taken into account in base64Write - return base64Write(this, string, offset, length) - - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return ucs2Write(this, string, offset, length) - - default: - if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) - encoding = ('' + encoding).toLowerCase() - loweredCase = true - } - } -} - -Buffer.prototype.toJSON = function toJSON () { - return { - type: 'Buffer', - data: Array.prototype.slice.call(this._arr || this, 0) - } -} - -function base64Slice (buf, start, end) { - if (start === 0 && end === buf.length) { - return base64.fromByteArray(buf) - } else { - return base64.fromByteArray(buf.slice(start, end)) - } -} - -function utf8Slice (buf, start, end) { - end = Math.min(buf.length, end) - var res = [] - - var i = start - while (i < end) { - var firstByte = buf[i] - var codePoint = null - var bytesPerSequence = (firstByte > 0xEF) ? 4 - : (firstByte > 0xDF) ? 3 - : (firstByte > 0xBF) ? 2 - : 1 - - if (i + bytesPerSequence <= end) { - var secondByte, thirdByte, fourthByte, tempCodePoint - - switch (bytesPerSequence) { - case 1: - if (firstByte < 0x80) { - codePoint = firstByte - } - break - case 2: - secondByte = buf[i + 1] - if ((secondByte & 0xC0) === 0x80) { - tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) - if (tempCodePoint > 0x7F) { - codePoint = tempCodePoint - } - } - break - case 3: - secondByte = buf[i + 1] - thirdByte = buf[i + 2] - if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { - tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) - if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { - codePoint = tempCodePoint - } - } - break - case 4: - secondByte = buf[i + 1] - thirdByte = buf[i + 2] - fourthByte = buf[i + 3] - if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { - tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) - if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { - codePoint = tempCodePoint - } - } - } - } - - if (codePoint === null) { - // we did not generate a valid codePoint so insert a - // replacement char (U+FFFD) and advance only 1 byte - codePoint = 0xFFFD - bytesPerSequence = 1 - } else if (codePoint > 0xFFFF) { - // encode to utf16 (surrogate pair dance) - codePoint -= 0x10000 - res.push(codePoint >>> 10 & 0x3FF | 0xD800) - codePoint = 0xDC00 | codePoint & 0x3FF - } - - res.push(codePoint) - i += bytesPerSequence - } - - return decodeCodePointsArray(res) -} - -// Based on http://stackoverflow.com/a/22747272/680742, the browser with -// the lowest limit is Chrome, with 0x10000 args. -// We go 1 magnitude less, for safety -var MAX_ARGUMENTS_LENGTH = 0x1000 - -function decodeCodePointsArray (codePoints) { - var len = codePoints.length - if (len <= MAX_ARGUMENTS_LENGTH) { - return String.fromCharCode.apply(String, codePoints) // avoid extra slice() - } - - // Decode in chunks to avoid "call stack size exceeded". - var res = '' - var i = 0 - while (i < len) { - res += String.fromCharCode.apply( - String, - codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) - ) - } - return res -} - -function asciiSlice (buf, start, end) { - var ret = '' - end = Math.min(buf.length, end) - - for (var i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i] & 0x7F) - } - return ret -} - -function latin1Slice (buf, start, end) { - var ret = '' - end = Math.min(buf.length, end) - - for (var i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i]) - } - return ret -} - -function hexSlice (buf, start, end) { - var len = buf.length - - if (!start || start < 0) start = 0 - if (!end || end < 0 || end > len) end = len - - var out = '' - for (var i = start; i < end; ++i) { - out += toHex(buf[i]) - } - return out -} - -function utf16leSlice (buf, start, end) { - var bytes = buf.slice(start, end) - var res = '' - for (var i = 0; i < bytes.length; i += 2) { - res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) - } - return res -} - -Buffer.prototype.slice = function slice (start, end) { - var len = this.length - start = ~~start - end = end === undefined ? len : ~~end - - if (start < 0) { - start += len - if (start < 0) start = 0 - } else if (start > len) { - start = len - } - - if (end < 0) { - end += len - if (end < 0) end = 0 - } else if (end > len) { - end = len - } - - if (end < start) end = start - - var newBuf - if (Buffer.TYPED_ARRAY_SUPPORT) { - newBuf = this.subarray(start, end) - newBuf.__proto__ = Buffer.prototype - } else { - var sliceLen = end - start - newBuf = new Buffer(sliceLen, undefined) - for (var i = 0; i < sliceLen; ++i) { - newBuf[i] = this[i + start] - } - } - - return newBuf -} - -/* - * Need to make sure that buffer isn't trying to write out of bounds. - */ -function checkOffset (offset, ext, length) { - if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') - if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') -} - -Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) - - var val = this[offset] - var mul = 1 - var i = 0 - while (++i < byteLength && (mul *= 0x100)) { - val += this[offset + i] * mul - } - - return val -} - -Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) { - checkOffset(offset, byteLength, this.length) - } - - var val = this[offset + --byteLength] - var mul = 1 - while (byteLength > 0 && (mul *= 0x100)) { - val += this[offset + --byteLength] * mul - } - - return val -} - -Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { - if (!noAssert) checkOffset(offset, 1, this.length) - return this[offset] -} - -Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 2, this.length) - return this[offset] | (this[offset + 1] << 8) -} - -Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 2, this.length) - return (this[offset] << 8) | this[offset + 1] -} - -Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - - return ((this[offset]) | - (this[offset + 1] << 8) | - (this[offset + 2] << 16)) + - (this[offset + 3] * 0x1000000) -} - -Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - - return (this[offset] * 0x1000000) + - ((this[offset + 1] << 16) | - (this[offset + 2] << 8) | - this[offset + 3]) -} - -Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) - - var val = this[offset] - var mul = 1 - var i = 0 - while (++i < byteLength && (mul *= 0x100)) { - val += this[offset + i] * mul - } - mul *= 0x80 - - if (val >= mul) val -= Math.pow(2, 8 * byteLength) - - return val -} - -Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) - - var i = byteLength - var mul = 1 - var val = this[offset + --i] - while (i > 0 && (mul *= 0x100)) { - val += this[offset + --i] * mul - } - mul *= 0x80 - - if (val >= mul) val -= Math.pow(2, 8 * byteLength) - - return val -} - -Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { - if (!noAssert) checkOffset(offset, 1, this.length) - if (!(this[offset] & 0x80)) return (this[offset]) - return ((0xff - this[offset] + 1) * -1) -} - -Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 2, this.length) - var val = this[offset] | (this[offset + 1] << 8) - return (val & 0x8000) ? val | 0xFFFF0000 : val -} - -Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 2, this.length) - var val = this[offset + 1] | (this[offset] << 8) - return (val & 0x8000) ? val | 0xFFFF0000 : val -} - -Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - - return (this[offset]) | - (this[offset + 1] << 8) | - (this[offset + 2] << 16) | - (this[offset + 3] << 24) -} - -Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - - return (this[offset] << 24) | - (this[offset + 1] << 16) | - (this[offset + 2] << 8) | - (this[offset + 3]) -} - -Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - return ieee754.read(this, offset, true, 23, 4) -} - -Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 4, this.length) - return ieee754.read(this, offset, false, 23, 4) -} - -Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 8, this.length) - return ieee754.read(this, offset, true, 52, 8) -} - -Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { - if (!noAssert) checkOffset(offset, 8, this.length) - return ieee754.read(this, offset, false, 52, 8) -} - -function checkInt (buf, value, offset, ext, max, min) { - if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') - if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') - if (offset + ext > buf.length) throw new RangeError('Index out of range') -} - -Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { - value = +value - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) { - var maxBytes = Math.pow(2, 8 * byteLength) - 1 - checkInt(this, value, offset, byteLength, maxBytes, 0) - } - - var mul = 1 - var i = 0 - this[offset] = value & 0xFF - while (++i < byteLength && (mul *= 0x100)) { - this[offset + i] = (value / mul) & 0xFF - } - - return offset + byteLength -} - -Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { - value = +value - offset = offset | 0 - byteLength = byteLength | 0 - if (!noAssert) { - var maxBytes = Math.pow(2, 8 * byteLength) - 1 - checkInt(this, value, offset, byteLength, maxBytes, 0) - } - - var i = byteLength - 1 - var mul = 1 - this[offset + i] = value & 0xFF - while (--i >= 0 && (mul *= 0x100)) { - this[offset + i] = (value / mul) & 0xFF - } - - return offset + byteLength -} - -Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) - if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) - this[offset] = (value & 0xff) - return offset + 1 -} - -function objectWriteUInt16 (buf, value, offset, littleEndian) { - if (value < 0) value = 0xffff + value + 1 - for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) { - buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> - (littleEndian ? i : 1 - i) * 8 - } -} - -Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - } else { - objectWriteUInt16(this, value, offset, true) - } - return offset + 2 -} - -Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value >>> 8) - this[offset + 1] = (value & 0xff) - } else { - objectWriteUInt16(this, value, offset, false) - } - return offset + 2 -} - -function objectWriteUInt32 (buf, value, offset, littleEndian) { - if (value < 0) value = 0xffffffff + value + 1 - for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) { - buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff - } -} - -Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset + 3] = (value >>> 24) - this[offset + 2] = (value >>> 16) - this[offset + 1] = (value >>> 8) - this[offset] = (value & 0xff) - } else { - objectWriteUInt32(this, value, offset, true) - } - return offset + 4 -} - -Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value >>> 24) - this[offset + 1] = (value >>> 16) - this[offset + 2] = (value >>> 8) - this[offset + 3] = (value & 0xff) - } else { - objectWriteUInt32(this, value, offset, false) - } - return offset + 4 -} - -Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) { - var limit = Math.pow(2, 8 * byteLength - 1) - - checkInt(this, value, offset, byteLength, limit - 1, -limit) - } - - var i = 0 - var mul = 1 - var sub = 0 - this[offset] = value & 0xFF - while (++i < byteLength && (mul *= 0x100)) { - if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { - sub = 1 - } - this[offset + i] = ((value / mul) >> 0) - sub & 0xFF - } - - return offset + byteLength -} - -Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) { - var limit = Math.pow(2, 8 * byteLength - 1) - - checkInt(this, value, offset, byteLength, limit - 1, -limit) - } - - var i = byteLength - 1 - var mul = 1 - var sub = 0 - this[offset + i] = value & 0xFF - while (--i >= 0 && (mul *= 0x100)) { - if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { - sub = 1 - } - this[offset + i] = ((value / mul) >> 0) - sub & 0xFF - } - - return offset + byteLength -} - -Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) - if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) - if (value < 0) value = 0xff + value + 1 - this[offset] = (value & 0xff) - return offset + 1 -} - -Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - } else { - objectWriteUInt16(this, value, offset, true) - } - return offset + 2 -} - -Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value >>> 8) - this[offset + 1] = (value & 0xff) - } else { - objectWriteUInt16(this, value, offset, false) - } - return offset + 2 -} - -Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - this[offset + 2] = (value >>> 16) - this[offset + 3] = (value >>> 24) - } else { - objectWriteUInt32(this, value, offset, true) - } - return offset + 4 -} - -Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { - value = +value - offset = offset | 0 - if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) - if (value < 0) value = 0xffffffff + value + 1 - if (Buffer.TYPED_ARRAY_SUPPORT) { - this[offset] = (value >>> 24) - this[offset + 1] = (value >>> 16) - this[offset + 2] = (value >>> 8) - this[offset + 3] = (value & 0xff) - } else { - objectWriteUInt32(this, value, offset, false) - } - return offset + 4 -} - -function checkIEEE754 (buf, value, offset, ext, max, min) { - if (offset + ext > buf.length) throw new RangeError('Index out of range') - if (offset < 0) throw new RangeError('Index out of range') -} - -function writeFloat (buf, value, offset, littleEndian, noAssert) { - if (!noAssert) { - checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) - } - ieee754.write(buf, value, offset, littleEndian, 23, 4) - return offset + 4 -} - -Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { - return writeFloat(this, value, offset, true, noAssert) -} - -Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { - return writeFloat(this, value, offset, false, noAssert) -} - -function writeDouble (buf, value, offset, littleEndian, noAssert) { - if (!noAssert) { - checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) - } - ieee754.write(buf, value, offset, littleEndian, 52, 8) - return offset + 8 -} - -Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { - return writeDouble(this, value, offset, true, noAssert) -} - -Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { - return writeDouble(this, value, offset, false, noAssert) -} - -// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) -Buffer.prototype.copy = function copy (target, targetStart, start, end) { - if (!start) start = 0 - if (!end && end !== 0) end = this.length - if (targetStart >= target.length) targetStart = target.length - if (!targetStart) targetStart = 0 - if (end > 0 && end < start) end = start - - // Copy 0 bytes; we're done - if (end === start) return 0 - if (target.length === 0 || this.length === 0) return 0 - - // Fatal error conditions - if (targetStart < 0) { - throw new RangeError('targetStart out of bounds') - } - if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') - if (end < 0) throw new RangeError('sourceEnd out of bounds') - - // Are we oob? - if (end > this.length) end = this.length - if (target.length - targetStart < end - start) { - end = target.length - targetStart + start - } - - var len = end - start - var i - - if (this === target && start < targetStart && targetStart < end) { - // descending copy from end - for (i = len - 1; i >= 0; --i) { - target[i + targetStart] = this[i + start] - } - } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { - // ascending copy from start - for (i = 0; i < len; ++i) { - target[i + targetStart] = this[i + start] - } - } else { - Uint8Array.prototype.set.call( - target, - this.subarray(start, start + len), - targetStart - ) - } - - return len -} - -// Usage: -// buffer.fill(number[, offset[, end]]) -// buffer.fill(buffer[, offset[, end]]) -// buffer.fill(string[, offset[, end]][, encoding]) -Buffer.prototype.fill = function fill (val, start, end, encoding) { - // Handle string cases: - if (typeof val === 'string') { - if (typeof start === 'string') { - encoding = start - start = 0 - end = this.length - } else if (typeof end === 'string') { - encoding = end - end = this.length - } - if (val.length === 1) { - var code = val.charCodeAt(0) - if (code < 256) { - val = code - } - } - if (encoding !== undefined && typeof encoding !== 'string') { - throw new TypeError('encoding must be a string') - } - if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { - throw new TypeError('Unknown encoding: ' + encoding) - } - } else if (typeof val === 'number') { - val = val & 255 - } - - // Invalid ranges are not set to a default, so can range check early. - if (start < 0 || this.length < start || this.length < end) { - throw new RangeError('Out of range index') - } - - if (end <= start) { - return this - } - - start = start >>> 0 - end = end === undefined ? this.length : end >>> 0 - - if (!val) val = 0 - - var i - if (typeof val === 'number') { - for (i = start; i < end; ++i) { - this[i] = val - } - } else { - var bytes = Buffer.isBuffer(val) - ? val - : utf8ToBytes(new Buffer(val, encoding).toString()) - var len = bytes.length - for (i = 0; i < end - start; ++i) { - this[i + start] = bytes[i % len] - } - } - - return this -} - -// HELPER FUNCTIONS -// ================ - -var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g - -function base64clean (str) { - // Node strips out invalid characters like \n and \t from the string, base64-js does not - str = stringtrim(str).replace(INVALID_BASE64_RE, '') - // Node converts strings with length < 2 to '' - if (str.length < 2) return '' - // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not - while (str.length % 4 !== 0) { - str = str + '=' - } - return str -} - -function stringtrim (str) { - if (str.trim) return str.trim() - return str.replace(/^\s+|\s+$/g, '') -} - -function toHex (n) { - if (n < 16) return '0' + n.toString(16) - return n.toString(16) -} - -function utf8ToBytes (string, units) { - units = units || Infinity - var codePoint - var length = string.length - var leadSurrogate = null - var bytes = [] - - for (var i = 0; i < length; ++i) { - codePoint = string.charCodeAt(i) - - // is surrogate component - if (codePoint > 0xD7FF && codePoint < 0xE000) { - // last char was a lead - if (!leadSurrogate) { - // no lead yet - if (codePoint > 0xDBFF) { - // unexpected trail - if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) - continue - } else if (i + 1 === length) { - // unpaired lead - if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) - continue - } - - // valid lead - leadSurrogate = codePoint - - continue - } - - // 2 leads in a row - if (codePoint < 0xDC00) { - if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) - leadSurrogate = codePoint - continue - } - - // valid surrogate pair - codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 - } else if (leadSurrogate) { - // valid bmp char, but last char was a lead - if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) - } - - leadSurrogate = null - - // encode utf8 - if (codePoint < 0x80) { - if ((units -= 1) < 0) break - bytes.push(codePoint) - } else if (codePoint < 0x800) { - if ((units -= 2) < 0) break - bytes.push( - codePoint >> 0x6 | 0xC0, - codePoint & 0x3F | 0x80 - ) - } else if (codePoint < 0x10000) { - if ((units -= 3) < 0) break - bytes.push( - codePoint >> 0xC | 0xE0, - codePoint >> 0x6 & 0x3F | 0x80, - codePoint & 0x3F | 0x80 - ) - } else if (codePoint < 0x110000) { - if ((units -= 4) < 0) break - bytes.push( - codePoint >> 0x12 | 0xF0, - codePoint >> 0xC & 0x3F | 0x80, - codePoint >> 0x6 & 0x3F | 0x80, - codePoint & 0x3F | 0x80 - ) - } else { - throw new Error('Invalid code point') - } - } - - return bytes -} - -function asciiToBytes (str) { - var byteArray = [] - for (var i = 0; i < str.length; ++i) { - // Node's code seems to be doing this and not & 0x7F.. - byteArray.push(str.charCodeAt(i) & 0xFF) - } - return byteArray -} - -function utf16leToBytes (str, units) { - var c, hi, lo - var byteArray = [] - for (var i = 0; i < str.length; ++i) { - if ((units -= 2) < 0) break - - c = str.charCodeAt(i) - hi = c >> 8 - lo = c % 256 - byteArray.push(lo) - byteArray.push(hi) - } - - return byteArray -} - -function base64ToBytes (str) { - return base64.toByteArray(base64clean(str)) -} - -function blitBuffer (src, dst, offset, length) { - for (var i = 0; i < length; ++i) { - if ((i + offset >= dst.length) || (i >= src.length)) break - dst[i + offset] = src[i] - } - return i -} - -function isnan (val) { - return val !== val // eslint-disable-line no-self-compare -} - -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(22))) - -/***/ }), -/* 11 */ -/***/ (function(module, exports, __webpack_require__) { - -exports = module.exports = __webpack_require__(3)(undefined); -// imports - - -// module -exports.push([module.i, ".pdf-viewer{background:url(" + __webpack_require__(15) + ");display:flex;flex-flow:column nowrap}", ""]); - -// exports - - -/***/ }), -/* 12 */ -/***/ (function(module, exports, __webpack_require__) { - -exports = module.exports = __webpack_require__(3)(undefined); -// imports - - -// module -exports.push([module.i, ".pdf-page{margin:8px auto 0;border-top:1px solid #ddd;border-bottom:1px solid #ddd;width:100%}.pdf-page:first-child{margin-top:0;border-top:0}.pdf-page:last-child{margin-bottom:0;border-bottom:0}", ""]); - -// exports - - -/***/ }), -/* 13 */ -/***/ (function(module, exports) { - -exports.read = function (buffer, offset, isLE, mLen, nBytes) { - var e, m - var eLen = nBytes * 8 - mLen - 1 - var eMax = (1 << eLen) - 1 - var eBias = eMax >> 1 - var nBits = -7 - var i = isLE ? (nBytes - 1) : 0 - var d = isLE ? -1 : 1 - var s = buffer[offset + i] - - i += d - - e = s & ((1 << (-nBits)) - 1) - s >>= (-nBits) - nBits += eLen - for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} - - m = e & ((1 << (-nBits)) - 1) - e >>= (-nBits) - nBits += mLen - for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} - - if (e === 0) { - e = 1 - eBias - } else if (e === eMax) { - return m ? NaN : ((s ? -1 : 1) * Infinity) - } else { - m = m + Math.pow(2, mLen) - e = e - eBias - } - return (s ? -1 : 1) * m * Math.pow(2, e - mLen) -} - -exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { - var e, m, c - var eLen = nBytes * 8 - mLen - 1 - var eMax = (1 << eLen) - 1 - var eBias = eMax >> 1 - var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) - var i = isLE ? 0 : (nBytes - 1) - var d = isLE ? 1 : -1 - var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 - - value = Math.abs(value) - - if (isNaN(value) || value === Infinity) { - m = isNaN(value) ? 1 : 0 - e = eMax - } else { - e = Math.floor(Math.log(value) / Math.LN2) - if (value * (c = Math.pow(2, -e)) < 1) { - e-- - c *= 2 - } - if (e + eBias >= 1) { - value += rt / c - } else { - value += rt * Math.pow(2, 1 - eBias) - } - if (value * c >= 2) { - e++ - c /= 2 - } - - if (e + eBias >= eMax) { - m = 0 - e = eMax - } else if (e + eBias >= 1) { - m = (value * c - 1) * Math.pow(2, mLen) - e = e + eBias - } else { - m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) - e = 0 - } - } - - for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} - - e = (e << mLen) | m - eLen += mLen - for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} - - buffer[offset + i - d] |= s * 128 -} - - -/***/ }), -/* 14 */ -/***/ (function(module, exports) { - -var toString = {}.toString; - -module.exports = Array.isArray || function (arr) { - return toString.call(arr) == '[object Array]'; -}; - - -/***/ }), -/* 15 */ -/***/ (function(module, exports) { - -module.exports = "data:image/gif;base64,R0lGODlhCgAKAIAAAOXl5f///yH5BAAAAAAALAAAAAAKAAoAAAIRhB2ZhxoM3GMSykqd1VltzxQAOw==" - -/***/ }), -/* 16 */ -/***/ (function(module, exports, __webpack_require__) { - - -/* styles */ -__webpack_require__(20) - -var Component = __webpack_require__(4)( - /* script */ - __webpack_require__(8), - /* template */ - __webpack_require__(18), - /* scopeId */ - null, - /* cssModules */ - null -) - -module.exports = Component.exports - - -/***/ }), -/* 17 */ -/***/ (function(module, exports) { - -module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; - return (_vm.hasPDF) ? _c('div', { - staticClass: "pdf-viewer" - }, _vm._l((_vm.pages), function(page, index) { - return _c('page', { - key: index, - attrs: { - "v-if": !_vm.loading, - "page": page, - "number": index + 1 - } - }) - })) : _vm._e() -},staticRenderFns: []} - -/***/ }), -/* 18 */ -/***/ (function(module, exports) { - -module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; - return _c('canvas', { - ref: "canvas", - staticClass: "pdf-page", - attrs: { - "data-page": _vm.number - } - }) -},staticRenderFns: []} - -/***/ }), -/* 19 */ -/***/ (function(module, exports, __webpack_require__) { - -// style-loader: Adds some css to the DOM by adding a <style> tag - -// load the styles -var content = __webpack_require__(11); -if(typeof content === 'string') content = [[module.i, content, '']]; -if(content.locals) module.exports = content.locals; -// add the styles to the DOM -var update = __webpack_require__(5)("59cf066f", content, true); -// Hot Module Replacement -if(false) { - // When the styles change, update the <style> tags - if(!content.locals) { - module.hot.accept("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() { - var newContent = require("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue"); - if(typeof newContent === 'string') newContent = [[module.id, newContent, '']]; - update(newContent); - }); - } - // When the module is disposed, remove the <style> tags - module.hot.dispose(function() { update(); }); -} - -/***/ }), -/* 20 */ -/***/ (function(module, exports, __webpack_require__) { - -// style-loader: Adds some css to the DOM by adding a <style> tag - -// load the styles -var content = __webpack_require__(12); -if(typeof content === 'string') content = [[module.i, content, '']]; -if(content.locals) module.exports = content.locals; -// add the styles to the DOM -var update = __webpack_require__(5)("09f1e2d8", content, true); -// Hot Module Replacement -if(false) { - // When the styles change, update the <style> tags - if(!content.locals) { - module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() { - var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue"); - if(typeof newContent === 'string') newContent = [[module.id, newContent, '']]; - update(newContent); - }); - } - // When the module is disposed, remove the <style> tags - module.hot.dispose(function() { update(); }); -} - -/***/ }), -/* 21 */ -/***/ (function(module, exports) { - -/** - * Translates the list format produced by css-loader into something - * easier to manipulate. - */ -module.exports = function listToStyles (parentId, list) { - var styles = [] - var newStyles = {} - for (var i = 0; i < list.length; i++) { - var item = list[i] - var id = item[0] - var css = item[1] - var media = item[2] - var sourceMap = item[3] - var part = { - id: parentId + ':' + i, - css: css, - media: media, - sourceMap: sourceMap - } - if (!newStyles[id]) { - styles.push(newStyles[id] = { id: id, parts: [part] }) - } else { - newStyles[id].parts.push(part) - } - } - return styles -} - - -/***/ }), -/* 22 */ -/***/ (function(module, exports) { - -var g; - -// This works in non-strict mode -g = (function() { - return this; -})(); - -try { - // This works if eval is allowed (see CSP) - g = g || Function("return this")() || (1,eval)("this"); -} catch(e) { - // This works if the window reference is available - if(typeof window === "object") - g = window; -} - -// g can still be undefined, but nothing to do about it... -// We return undefined, instead of nothing here, so it's -// easier to handle this case. if(!global) { ...} - -module.exports = g; - - -/***/ }), -/* 23 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var PDF = __webpack_require__(6); -var pdfjsLib = __webpack_require__(2); - -module.exports = { - install: function install(_vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - pdfjsLib.PDFJS.workerSrc = options.workerSrc || ''; - _vue.component('pdf-lab', PDF); - } -}; - -/***/ }) -/******/ ]); });
\ No newline at end of file diff --git a/vendor/assets/javascripts/pdf.min.js b/vendor/assets/javascripts/pdf.min.js new file mode 100755 index 00000000000..271a471fd49 --- /dev/null +++ b/vendor/assets/javascripts/pdf.min.js @@ -0,0 +1,6 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf",[],e):"object"==typeof exports?exports["pdfjs-dist/build/pdf"]=e():t["pdfjs-dist/build/pdf"]=t.pdfjsDistBuildPdf=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=13)}([function(t,e,n){"use strict";(function(t){function r(t){nt=t}function i(){return nt}function a(t){nt>=$.infos&&console.log("Info: "+t)}function s(t){nt>=$.warnings&&console.log("Warning: "+t)}function o(t){console.log("Deprecated API usage: "+t)}function c(t){throw nt>=$.errors&&(console.log("Error: "+t),console.log(l())),new Error(t)}function l(){try{throw new Error}catch(t){return t.stack?t.stack.split("\n").slice(2).join("\n"):""}}function h(t,e){t||c(e)}function u(t,e){try{var n=new URL(t);if(!n.origin||"null"===n.origin)return!1}catch(t){return!1}var r=new URL(e,n);return n.origin===r.origin}function d(t){if(!t)return!1;switch(t.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}function f(t,e){if(!t)return null;try{var n=e?new URL(t,e):new URL(t);if(d(n))return n}catch(t){}return null}function p(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!1}),n}function g(t){var e;return function(){return t&&(e=Object.create(null),t(e),t=null),e}}function m(t){return"string"!=typeof t?(s("The argument for removeNullCharacters must be a string."),t):t.replace(ft,"")}function A(t){h(null!==t&&"object"==typeof t&&void 0!==t.length,"Invalid argument for bytesToString");var e=t.length;if(e<8192)return String.fromCharCode.apply(null,t);for(var n=[],r=0;r<e;r+=8192){var i=Math.min(r+8192,e),a=t.subarray(r,i);n.push(String.fromCharCode.apply(null,a))}return n.join("")}function v(t){h("string"==typeof t,"Invalid argument for stringToBytes");for(var e=t.length,n=new Uint8Array(e),r=0;r<e;++r)n[r]=255&t.charCodeAt(r);return n}function b(t){return void 0!==t.length?t.length:(h(void 0!==t.byteLength),t.byteLength)}function y(t){if(1===t.length&&t[0]instanceof Uint8Array)return t[0];var e,n,r,i=0,a=t.length;for(e=0;e<a;e++)n=t[e],r=b(n),i+=r;var s=0,o=new Uint8Array(i);for(e=0;e<a;e++)n=t[e],n instanceof Uint8Array||(n="string"==typeof n?v(n):new Uint8Array(n)),r=n.byteLength,o.set(n,s),s+=r;return o}function x(t){return String.fromCharCode(t>>24&255,t>>16&255,t>>8&255,255&t)}function S(t){for(var e=1,n=0;t>e;)e<<=1,n++;return n}function w(t,e){return t[e]<<24>>24}function k(t,e){return t[e]<<8|t[e+1]}function C(t,e){return(t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])>>>0}function _(){var t=new Uint8Array(2);return t[0]=1,1===new Uint16Array(t.buffer)[0]}function T(){try{return new Function(""),!0}catch(t){return!1}}function P(t){var e,n=t.length,r=[];if("þ"===t[0]&&"ÿ"===t[1])for(e=2;e<n;e+=2)r.push(String.fromCharCode(t.charCodeAt(e)<<8|t.charCodeAt(e+1)));else for(e=0;e<n;++e){var i=vt[t.charCodeAt(e)];r.push(i?String.fromCharCode(i):t.charAt(e))}return r.join("")}function L(t){return decodeURIComponent(escape(t))}function E(t){return unescape(encodeURIComponent(t))}function R(t){for(var e in t)return!1;return!0}function I(t){return"boolean"==typeof t}function F(t){return"number"==typeof t&&(0|t)===t}function O(t){return"number"==typeof t}function M(t){return"string"==typeof t}function D(t){return t instanceof Array}function N(t){return"object"==typeof t&&null!==t&&void 0!==t.byteLength}function j(t){return 32===t||9===t||13===t||10===t}function U(){return"undefined"==typeof __pdfjsdev_webpack__&&("object"==typeof process&&process+""=="[object process]")}function B(){var t={};return t.promise=new Promise(function(e,n){t.resolve=e,t.reject=n}),t}function W(t,e,n){this.sourceName=t,this.targetName=e,this.comObj=n,this.callbackIndex=1,this.postMessageTransfers=!0;var r=this.callbacksCapabilities=Object.create(null),i=this.actionHandler=Object.create(null);this._onComObjOnMessage=function(t){var e=t.data;if(e.targetName===this.sourceName)if(e.isReply){var a=e.callbackId;if(e.callbackId in r){var s=r[a];delete r[a],"error"in e?s.reject(e.error):s.resolve(e.data)}else c("Cannot resolve callback "+a)}else if(e.action in i){var o=i[e.action];if(e.callbackId){var l=this.sourceName,h=e.sourceName;Promise.resolve().then(function(){return o[0].call(o[1],e.data)}).then(function(t){n.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:e.callbackId,data:t})},function(t){t instanceof Error&&(t+=""),n.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:e.callbackId,error:t})})}else o[0].call(o[1],e.data)}else c("Unknown action from worker: "+e.action)}.bind(this),n.addEventListener("message",this._onComObjOnMessage)}function G(t,e,n){var r=new Image;r.onload=function(){n.resolve(t,r)},r.onerror=function(){n.resolve(t,null),s("Error during JPEG image loading")},r.src=e}var X=(n(14),"undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:void 0),H=[.001,0,0,.001,0,0],z={FILL:0,STROKE:1,FILL_STROKE:2,INVISIBLE:3,FILL_ADD_TO_PATH:4,STROKE_ADD_TO_PATH:5,FILL_STROKE_ADD_TO_PATH:6,ADD_TO_PATH:7,FILL_STROKE_MASK:3,ADD_TO_PATH_FLAG:4},Y={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},q={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},V={INVISIBLE:1,HIDDEN:2,PRINT:4,NOZOOM:8,NOROTATE:16,NOVIEW:32,READONLY:64,LOCKED:128,TOGGLENOVIEW:256,LOCKEDCONTENTS:512},J={READONLY:1,REQUIRED:2,NOEXPORT:4,MULTILINE:4096,PASSWORD:8192,NOTOGGLETOOFF:16384,RADIO:32768,PUSHBUTTON:65536,COMBO:131072,EDIT:262144,SORT:524288,FILESELECT:1048576,MULTISELECT:2097152,DONOTSPELLCHECK:4194304,DONOTSCROLL:8388608,COMB:16777216,RICHTEXT:33554432,RADIOSINUNISON:33554432,COMMITONSELCHANGE:67108864},Q={SOLID:1,DASHED:2,BEVELED:3,INSET:4,UNDERLINE:5},K={UNKNOWN:0,FLATE:1,LZW:2,DCT:3,JPX:4,JBIG:5,A85:6,AHX:7,CCF:8,RL:9},Z={UNKNOWN:0,TYPE1:1,TYPE1C:2,CIDFONTTYPE0:3,CIDFONTTYPE0C:4,TRUETYPE:5,CIDFONTTYPE2:6,TYPE3:7,OPENTYPE:8,TYPE0:9,MMTYPE1:10},$={errors:0,warnings:1,infos:5},tt={NONE:0,BINARY:1,STREAM:2},et={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotations:78,endAnnotations:79,beginAnnotation:80,endAnnotation:81,paintJpegXObject:82,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91},nt=$.warnings,rt={unknown:"unknown",forms:"forms",javaScript:"javaScript",smask:"smask",shadingPattern:"shadingPattern",font:"font"},it={NEED_PASSWORD:1,INCORRECT_PASSWORD:2},at=function(){function t(t,e){this.name="PasswordException",this.message=t,this.code=e}return t.prototype=new Error,t.constructor=t,t}(),st=function(){function t(t,e){this.name="UnknownErrorException",this.message=t,this.details=e}return t.prototype=new Error,t.constructor=t,t}(),ot=function(){function t(t){this.name="InvalidPDFException",this.message=t}return t.prototype=new Error,t.constructor=t,t}(),ct=function(){function t(t){this.name="MissingPDFException",this.message=t}return t.prototype=new Error,t.constructor=t,t}(),lt=function(){function t(t,e){this.name="UnexpectedResponseException",this.message=t,this.status=e}return t.prototype=new Error,t.constructor=t,t}(),ht=function(){function t(t){this.message=t}return t.prototype=new Error,t.prototype.name="NotImplementedException",t.constructor=t,t}(),ut=function(){function t(t,e){this.begin=t,this.end=e,this.message="Missing data ["+t+", "+e+")"}return t.prototype=new Error,t.prototype.name="MissingDataException",t.constructor=t,t}(),dt=function(){function t(t){this.message=t}return t.prototype=new Error,t.prototype.name="XRefParseException",t.constructor=t,t}(),ft=/\x00/g,pt=function(){function t(t,e){this.buffer=t,this.byteLength=t.length,this.length=void 0===e?this.byteLength>>2:e,n(this.length)}function e(t){return{get:function(){var e=this.buffer,n=t<<2;return(e[n]|e[n+1]<<8|e[n+2]<<16|e[n+3]<<24)>>>0},set:function(e){var n=this.buffer,r=t<<2;n[r]=255&e,n[r+1]=e>>8&255,n[r+2]=e>>16&255,n[r+3]=e>>>24&255}}}function n(n){for(;r<n;)Object.defineProperty(t.prototype,r,e(r)),r++}t.prototype=Object.create(null);var r=0;return t}();e.Uint32ArrayView=pt;var gt=[1,0,0,1,0,0],mt=function(){function t(){}var e=["rgb(",0,",",0,",",0,")"];t.makeCssRgb=function(t,n,r){return e[1]=t,e[3]=n,e[5]=r,e.join("")},t.transform=function(t,e){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],t[0]*e[4]+t[2]*e[5]+t[4],t[1]*e[4]+t[3]*e[5]+t[5]]},t.applyTransform=function(t,e){return[t[0]*e[0]+t[1]*e[2]+e[4],t[0]*e[1]+t[1]*e[3]+e[5]]},t.applyInverseTransform=function(t,e){var n=e[0]*e[3]-e[1]*e[2];return[(t[0]*e[3]-t[1]*e[2]+e[2]*e[5]-e[4]*e[3])/n,(-t[0]*e[1]+t[1]*e[0]+e[4]*e[1]-e[5]*e[0])/n]},t.getAxialAlignedBoundingBox=function(e,n){var r=t.applyTransform(e,n),i=t.applyTransform(e.slice(2,4),n),a=t.applyTransform([e[0],e[3]],n),s=t.applyTransform([e[2],e[1]],n);return[Math.min(r[0],i[0],a[0],s[0]),Math.min(r[1],i[1],a[1],s[1]),Math.max(r[0],i[0],a[0],s[0]),Math.max(r[1],i[1],a[1],s[1])]},t.inverseTransform=function(t){var e=t[0]*t[3]-t[1]*t[2];return[t[3]/e,-t[1]/e,-t[2]/e,t[0]/e,(t[2]*t[5]-t[4]*t[3])/e,(t[4]*t[1]-t[5]*t[0])/e]},t.apply3dTransform=function(t,e){return[t[0]*e[0]+t[1]*e[1]+t[2]*e[2],t[3]*e[0]+t[4]*e[1]+t[5]*e[2],t[6]*e[0]+t[7]*e[1]+t[8]*e[2]]},t.singularValueDecompose2dScale=function(t){var e=[t[0],t[2],t[1],t[3]],n=t[0]*e[0]+t[1]*e[2],r=t[0]*e[1]+t[1]*e[3],i=t[2]*e[0]+t[3]*e[2],a=t[2]*e[1]+t[3]*e[3],s=(n+a)/2,o=Math.sqrt((n+a)*(n+a)-4*(n*a-i*r))/2,c=s+o||1,l=s-o||1;return[Math.sqrt(c),Math.sqrt(l)]},t.normalizeRect=function(t){var e=t.slice(0);return t[0]>t[2]&&(e[0]=t[2],e[2]=t[0]),t[1]>t[3]&&(e[1]=t[3],e[3]=t[1]),e},t.intersect=function(e,n){function r(t,e){return t-e}var i=[e[0],e[2],n[0],n[2]].sort(r),a=[e[1],e[3],n[1],n[3]].sort(r),s=[];return e=t.normalizeRect(e),n=t.normalizeRect(n),(i[0]===e[0]&&i[1]===n[0]||i[0]===n[0]&&i[1]===e[0])&&(s[0]=i[1],s[2]=i[2],(a[0]===e[1]&&a[1]===n[1]||a[0]===n[1]&&a[1]===e[1])&&(s[1]=a[1],s[3]=a[2],s))},t.sign=function(t){return t<0?-1:1};var n=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"];return t.toRoman=function(t,e){h(F(t)&&t>0,"The number should be a positive integer.");for(var r,i=[];t>=1e3;)t-=1e3,i.push("M");r=t/100|0,t%=100,i.push(n[r]),r=t/10|0,t%=10,i.push(n[10+r]),i.push(n[20+t]);var a=i.join("");return e?a.toLowerCase():a},t.appendToArray=function(t,e){Array.prototype.push.apply(t,e)},t.prependToArray=function(t,e){Array.prototype.unshift.apply(t,e)},t.extendObj=function(t,e){for(var n in e)t[n]=e[n]},t.getInheritableProperty=function(t,e,n){for(;t&&!t.has(e);)t=t.get("Parent");return t?n?t.getArray(e):t.get(e):null},t.inherit=function(t,e,n){t.prototype=Object.create(e.prototype),t.prototype.constructor=t;for(var r in n)t.prototype[r]=n[r]},t.loadScript=function(t,e){var n=document.createElement("script"),r=!1;n.setAttribute("src",t),e&&(n.onload=function(){r||e(),r=!0}),document.getElementsByTagName("head")[0].appendChild(n)},t}(),At=function(){function t(t,e,n,r,i,a){this.viewBox=t,this.scale=e,this.rotation=n,this.offsetX=r,this.offsetY=i;var s,o,c,l,h=(t[2]+t[0])/2,u=(t[3]+t[1])/2;switch(n%=360,n=n<0?n+360:n){case 180:s=-1,o=0,c=0,l=1;break;case 90:s=0,o=1,c=1,l=0;break;case 270:s=0,o=-1,c=-1,l=0;break;default:s=1,o=0,c=0,l=-1}a&&(c=-c,l=-l);var d,f,p,g;0===s?(d=Math.abs(u-t[1])*e+r,f=Math.abs(h-t[0])*e+i,p=Math.abs(t[3]-t[1])*e,g=Math.abs(t[2]-t[0])*e):(d=Math.abs(h-t[0])*e+r,f=Math.abs(u-t[1])*e+i,p=Math.abs(t[2]-t[0])*e,g=Math.abs(t[3]-t[1])*e),this.transform=[s*e,o*e,c*e,l*e,d-s*e*h-c*e*u,f-o*e*h-l*e*u],this.width=p,this.height=g,this.fontScale=e}return t.prototype={clone:function(e){e=e||{};var n="scale"in e?e.scale:this.scale,r="rotation"in e?e.rotation:this.rotation;return new t(this.viewBox.slice(),n,r,this.offsetX,this.offsetY,e.dontFlip)},convertToViewportPoint:function(t,e){return mt.applyTransform([t,e],this.transform)},convertToViewportRectangle:function(t){var e=mt.applyTransform([t[0],t[1]],this.transform),n=mt.applyTransform([t[2],t[3]],this.transform);return[e[0],e[1],n[0],n[1]]},convertToPdfPoint:function(t,e){return mt.applyInverseTransform([t,e],this.transform)}},t}(),vt=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],bt=function(){function t(t,e,n){for(;t.length<n;)t+=e;return t}function e(){this.started=Object.create(null),this.times=[],this.enabled=!0}return e.prototype={time:function(t){this.enabled&&(t in this.started&&s("Timer is already running for "+t),this.started[t]=Date.now())},timeEnd:function(t){this.enabled&&(t in this.started||s("Timer has not been started for "+t),this.times.push({name:t,start:this.started[t],end:Date.now()}),delete this.started[t])},toString:function(){var e,n,r=this.times,i="",a=0;for(e=0,n=r.length;e<n;++e){var s=r[e].name;s.length>a&&(a=s.length)}for(e=0,n=r.length;e<n;++e){var o=r[e],c=o.end-o.start;i+=t(o.name," ",a)+" "+c+"ms\n"}return i}},e}(),yt=function(t,e){if("undefined"!=typeof Blob)return new Blob([t],{type:e});s('The "Blob" constructor is not supported.')},xt=function(){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return function(e,n,r){if(!r&&"undefined"!=typeof URL&&URL.createObjectURL){var i=yt(e,n);return URL.createObjectURL(i)}for(var a="data:"+n+";base64,",s=0,o=e.length;s<o;s+=3){var c=255&e[s],l=255&e[s+1],h=255&e[s+2],u=c>>2,d=(3&c)<<4|l>>4,f=s+1<o?(15&l)<<2|h>>6:64,p=s+2<o?63&h:64;a+=t[u]+t[d]+t[f]+t[p]}return a}}();W.prototype={on:function(t,e,n){var r=this.actionHandler;r[t]&&c('There is already an actionName called "'+t+'"'),r[t]=[e,n]},send:function(t,e,n){var r={sourceName:this.sourceName,targetName:this.targetName,action:t,data:e};this.postMessage(r,n)},sendWithPromise:function(t,e,n){var r=this.callbackIndex++,i={sourceName:this.sourceName,targetName:this.targetName,action:t,data:e,callbackId:r},a=B();this.callbacksCapabilities[r]=a;try{this.postMessage(i,n)}catch(t){a.reject(t)}return a.promise},postMessage:function(t,e){e&&this.postMessageTransfers?this.comObj.postMessage(t,e):this.comObj.postMessage(t)},destroy:function(){this.comObj.removeEventListener("message",this._onComObjOnMessage)}},e.FONT_IDENTITY_MATRIX=H,e.IDENTITY_MATRIX=gt,e.OPS=et,e.VERBOSITY_LEVELS=$,e.UNSUPPORTED_FEATURES=rt,e.AnnotationBorderStyleType=Q,e.AnnotationFieldFlag=J,e.AnnotationFlag=V,e.AnnotationType=q,e.FontType=Z,e.ImageKind=Y,e.CMapCompressionType=tt,e.InvalidPDFException=ot,e.MessageHandler=W,e.MissingDataException=ut,e.MissingPDFException=ct,e.NotImplementedException=ht,e.PageViewport=At,e.PasswordException=at,e.PasswordResponses=it,e.StatTimer=bt,e.StreamType=K,e.TextRenderingMode=z,e.UnexpectedResponseException=lt,e.UnknownErrorException=st,e.Util=mt,e.XRefParseException=dt,e.arrayByteLength=b,e.arraysToBytes=y,e.assert=h,e.bytesToString=A,e.createBlob=yt,e.createPromiseCapability=B,e.createObjectURL=xt,e.deprecated=o,e.error=c,e.getLookupTableFactory=g,e.getVerbosityLevel=i,e.globalScope=X,e.info=a,e.isArray=D,e.isArrayBuffer=N,e.isBool=I,e.isEmptyObj=R,e.isInt=F,e.isNum=O,e.isString=M,e.isSpace=j,e.isNodeJS=U,e.isSameOrigin=u,e.createValidAbsoluteUrl=f,e.isLittleEndian=_,e.isEvalSupported=T,e.loadJpegStream=G,e.log2=S,e.readInt8=w,e.readUint16=k,e.readUint32=C,e.removeNullCharacters=m,e.setVerbosityLevel=r,e.shadow=p,e.string32=x,e.stringToBytes=v,e.stringToPDFString=P,e.stringToUTF8String=L,e.utf8StringToString=E,e.warn=s}).call(e,n(6))},function(t,e,n){"use strict";function r(){}function i(t,e){var n=e&&e.url;if(t.href=t.title=n?u(n):"",n){var r=e.target;void 0===r&&(r=s("externalLinkTarget")),t.target=w[r];var i=e.rel;void 0===i&&(i=s("externalLinkRel")),t.rel=i}}function a(t){var e=t.indexOf("#"),n=t.indexOf("?"),r=Math.min(e>0?e:t.length,n>0?n:t.length);return t.substring(t.lastIndexOf("/",r)+1,r)}function s(t){var e=l.globalScope.PDFJS;switch(t){case"pdfBug":return!!e&&e.pdfBug;case"disableAutoFetch":return!!e&&e.disableAutoFetch;case"disableStream":return!!e&&e.disableStream;case"disableRange":return!!e&&e.disableRange;case"disableFontFace":return!!e&&e.disableFontFace;case"disableCreateObjectURL":return!!e&&e.disableCreateObjectURL;case"disableWebGL":return!e||e.disableWebGL;case"cMapUrl":return e?e.cMapUrl:null;case"cMapPacked":return!!e&&e.cMapPacked;case"postMessageTransfers":return!e||e.postMessageTransfers;case"workerPort":return e?e.workerPort:null;case"workerSrc":return e?e.workerSrc:null;case"disableWorker":return!!e&&e.disableWorker;case"maxImageSize":return e?e.maxImageSize:-1;case"imageResourcesPath":return e?e.imageResourcesPath:"";case"isEvalSupported":return!e||e.isEvalSupported;case"externalLinkTarget":if(!e)return S.NONE;switch(e.externalLinkTarget){case S.NONE:case S.SELF:case S.BLANK:case S.PARENT:case S.TOP:return e.externalLinkTarget}return d("PDFJS.externalLinkTarget is invalid: "+e.externalLinkTarget),e.externalLinkTarget=S.NONE,S.NONE;case"externalLinkRel":return e?e.externalLinkRel:A;case"enableStats":return!(!e||!e.enableStats);case"pdfjsNext":return!(!e||!e.pdfjsNext);default:throw new Error("Unknown default setting: "+t)}}function o(){switch(s("externalLinkTarget")){case S.NONE:return!1;case S.SELF:case S.BLANK:case S.PARENT:case S.TOP:return!0}}function c(t,e){return f("isValidUrl(), please use createValidAbsoluteUrl() instead."),null!==p(t,e?"http://example.com":null)}var l=n(0),h=l.assert,u=l.removeNullCharacters,d=l.warn,f=l.deprecated,p=l.createValidAbsoluteUrl,g=l.stringToBytes,m=l.CMapCompressionType,A="noopener noreferrer nofollow";r.prototype={create:function(t,e){h(t>0&&e>0,"invalid canvas size");var n=document.createElement("canvas"),r=n.getContext("2d");return n.width=t,n.height=e,{canvas:n,context:r}},reset:function(t,e,n){h(t.canvas,"canvas is not specified"),h(e>0&&n>0,"invalid canvas size"),t.canvas.width=e,t.canvas.height=n},destroy:function(t){h(t.canvas,"canvas is not specified"),t.canvas.width=0,t.canvas.height=0,t.canvas=null,t.context=null}};var v,b=function(){function t(t){this.baseUrl=t.baseUrl||null,this.isCompressed=t.isCompressed||!1}return t.prototype={fetch:function(t){var e=t.name;return e?new Promise(function(t,n){var r=this.baseUrl+e+(this.isCompressed?".bcmap":""),i=new XMLHttpRequest;i.open("GET",r,!0),this.isCompressed&&(i.responseType="arraybuffer"),i.onreadystatechange=function(){if(i.readyState===XMLHttpRequest.DONE){if(200===i.status||0===i.status){var e;if(this.isCompressed&&i.response?e=new Uint8Array(i.response):!this.isCompressed&&i.responseText&&(e=g(i.responseText)),e)return void t({cMapData:e,compressionType:this.isCompressed?m.BINARY:m.NONE})}n(new Error("Unable to load "+(this.isCompressed?"binary ":"")+"CMap at: "+r))}}.bind(this),i.send(null)}.bind(this)):Promise.reject(new Error("CMap name must be specified."))}},t}(),y=function(){function t(){}var e=["ms","Moz","Webkit","O"],n=Object.create(null);return t.getProp=function(t,r){if(1===arguments.length&&"string"==typeof n[t])return n[t];r=r||document.documentElement;var i,a,s=r.style;if("string"==typeof s[t])return n[t]=t;a=t.charAt(0).toUpperCase()+t.slice(1);for(var o=0,c=e.length;o<c;o++)if(i=e[o]+a,"string"==typeof s[i])return n[t]=i;return n[t]="undefined"},t.setProp=function(t,e,n){var r=this.getProp(t);"undefined"!==r&&(e.style[r]=n)},t}(),x=function(){function t(t,e){this.message=t,this.type=e}return t.prototype=new Error,t.prototype.name="RenderingCancelledException",t.constructor=t,t}();v=function(){var t=document.createElement("canvas");return t.width=t.height=1,void 0!==t.getContext("2d").createImageData(1,1).data.buffer};var S={NONE:0,SELF:1,BLANK:2,PARENT:3,TOP:4},w=["","_self","_blank","_parent","_top"];e.CustomStyle=y,e.addLinkAttributes=i,e.isExternalLinkTargetSet=o,e.isValidUrl=c,e.getFilenameFromUrl=a,e.LinkTarget=S,e.RenderingCancelledException=x,e.hasCanvasTypedArrays=v,e.getDefaultSetting=s,e.DEFAULT_LINK_REL=A,e.DOMCanvasFactory=r,e.DOMCMapReaderFactory=b},function(t,e,n){"use strict";function r(){}var i=n(0),a=n(1),s=i.AnnotationBorderStyleType,o=i.AnnotationType,c=i.stringToPDFString,l=i.Util,h=a.addLinkAttributes,u=a.LinkTarget,d=a.getFilenameFromUrl,f=i.warn,p=a.CustomStyle,g=a.getDefaultSetting;r.prototype={create:function(t){switch(t.data.annotationType){case o.LINK:return new A(t);case o.TEXT:return new v(t);case o.WIDGET:switch(t.data.fieldType){case"Tx":return new y(t);case"Btn":if(t.data.radioButton)return new S(t);if(t.data.checkBox)return new x(t);f("Unimplemented button widget annotation: pushbutton");break;case"Ch":return new w(t)}return new b(t);case o.POPUP:return new k(t);case o.HIGHLIGHT:return new _(t);case o.UNDERLINE:return new T(t);case o.SQUIGGLY:return new P(t);case o.STRIKEOUT:return new L(t);case o.FILEATTACHMENT:return new E(t);default:return new m(t)}}};var m=function(){function t(t,e){this.isRenderable=e||!1,this.data=t.data,this.layer=t.layer,this.page=t.page,this.viewport=t.viewport,this.linkService=t.linkService,this.downloadManager=t.downloadManager,this.imageResourcesPath=t.imageResourcesPath,this.renderInteractiveForms=t.renderInteractiveForms,e&&(this.container=this._createContainer())}return t.prototype={_createContainer:function(){var t=this.data,e=this.page,n=this.viewport,r=document.createElement("section"),i=t.rect[2]-t.rect[0],a=t.rect[3]-t.rect[1];r.setAttribute("data-annotation-id",t.id);var o=l.normalizeRect([t.rect[0],e.view[3]-t.rect[1]+e.view[1],t.rect[2],e.view[3]-t.rect[3]+e.view[1]]);if(p.setProp("transform",r,"matrix("+n.transform.join(",")+")"),p.setProp("transformOrigin",r,-o[0]+"px "+-o[1]+"px"),t.borderStyle.width>0){r.style.borderWidth=t.borderStyle.width+"px",t.borderStyle.style!==s.UNDERLINE&&(i-=2*t.borderStyle.width,a-=2*t.borderStyle.width);var c=t.borderStyle.horizontalCornerRadius,h=t.borderStyle.verticalCornerRadius;if(c>0||h>0){var u=c+"px / "+h+"px";p.setProp("borderRadius",r,u)}switch(t.borderStyle.style){case s.SOLID:r.style.borderStyle="solid";break;case s.DASHED:r.style.borderStyle="dashed";break;case s.BEVELED:f("Unimplemented border style: beveled");break;case s.INSET:f("Unimplemented border style: inset");break;case s.UNDERLINE:r.style.borderBottomStyle="solid"}t.color?r.style.borderColor=l.makeCssRgb(0|t.color[0],0|t.color[1],0|t.color[2]):r.style.borderWidth=0}return r.style.left=o[0]+"px",r.style.top=o[1]+"px",r.style.width=i+"px",r.style.height=a+"px",r},_createPopup:function(t,e,n){e||(e=document.createElement("div"),e.style.height=t.style.height,e.style.width=t.style.width,t.appendChild(e));var r=new C({container:t,trigger:e,color:n.color,title:n.title,contents:n.contents,hideWrapper:!0}),i=r.render();i.style.left=t.style.width,t.appendChild(i)},render:function(){throw new Error("Abstract method AnnotationElement.render called")}},t}(),A=function(){function t(t){m.call(this,t,!0)}return l.inherit(t,m,{render:function(){this.container.className="linkAnnotation";var t=document.createElement("a");return h(t,{url:this.data.url,target:this.data.newWindow?u.BLANK:void 0}),this.data.url||(this.data.action?this._bindNamedAction(t,this.data.action):this._bindLink(t,this.data.dest)),this.container.appendChild(t),this.container},_bindLink:function(t,e){var n=this;t.href=this.linkService.getDestinationHash(e),t.onclick=function(){return e&&n.linkService.navigateTo(e),!1},e&&(t.className="internalLink")},_bindNamedAction:function(t,e){var n=this;t.href=this.linkService.getAnchorUrl(""),t.onclick=function(){return n.linkService.executeNamedAction(e),!1},t.className="internalLink"}}),t}(),v=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){this.container.className="textAnnotation";var t=document.createElement("img");return t.style.height=this.container.style.height,t.style.width=this.container.style.width,t.src=this.imageResourcesPath+"annotation-"+this.data.name.toLowerCase()+".svg",t.alt="[{{type}} Annotation]",t.dataset.l10nId="text_annotation_type",t.dataset.l10nArgs=JSON.stringify({type:this.data.name}),this.data.hasPopup||this._createPopup(this.container,t,this.data),this.container.appendChild(t),this.container}}),t}(),b=function(){function t(t,e){m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container}}),t}(),y=function(){function t(t){var e=t.renderInteractiveForms||!t.data.hasAppearance&&!!t.data.fieldValue;b.call(this,t,e)}var e=["left","center","right"];return l.inherit(t,b,{render:function(){this.container.className="textWidgetAnnotation";var t=null;if(this.renderInteractiveForms){if(this.data.multiLine?(t=document.createElement("textarea"),t.textContent=this.data.fieldValue):(t=document.createElement("input"),t.type="text",t.setAttribute("value",this.data.fieldValue)),t.disabled=this.data.readOnly,null!==this.data.maxLen&&(t.maxLength=this.data.maxLen),this.data.comb){var n=this.data.rect[2]-this.data.rect[0],r=n/this.data.maxLen;t.classList.add("comb"),t.style.letterSpacing="calc("+r+"px - 1ch)"}}else{t=document.createElement("div"),t.textContent=this.data.fieldValue,t.style.verticalAlign="middle",t.style.display="table-cell";var i=null;this.data.fontRefName&&(i=this.page.commonObjs.getData(this.data.fontRefName)),this._setTextStyle(t,i)}return null!==this.data.textAlignment&&(t.style.textAlign=e[this.data.textAlignment]),this.container.appendChild(t),this.container},_setTextStyle:function(t,e){var n=t.style;if(n.fontSize=this.data.fontSize+"px",n.direction=this.data.fontDirection<0?"rtl":"ltr",e){n.fontWeight=e.black?e.bold?"900":"bold":e.bold?"bold":"normal",n.fontStyle=e.italic?"italic":"normal";var r=e.loadedName?'"'+e.loadedName+'", ':"",i=e.fallbackName||"Helvetica, sans-serif";n.fontFamily=r+i}}}),t}(),x=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="buttonWidgetAnnotation checkBox";var t=document.createElement("input");return t.disabled=this.data.readOnly,t.type="checkbox",this.data.fieldValue&&"Off"!==this.data.fieldValue&&t.setAttribute("checked",!0),this.container.appendChild(t),this.container}}),t}(),S=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="buttonWidgetAnnotation radioButton";var t=document.createElement("input");return t.disabled=this.data.readOnly,t.type="radio",t.name=this.data.fieldName,this.data.fieldValue===this.data.buttonValue&&t.setAttribute("checked",!0),this.container.appendChild(t),this.container}}),t}(),w=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="choiceWidgetAnnotation";var t=document.createElement("select");t.disabled=this.data.readOnly,this.data.combo||(t.size=this.data.options.length,this.data.multiSelect&&(t.multiple=!0));for(var e=0,n=this.data.options.length;e<n;e++){var r=this.data.options[e],i=document.createElement("option");i.textContent=r.displayValue,i.value=r.exportValue,this.data.fieldValue.indexOf(r.displayValue)>=0&&i.setAttribute("selected",!0),t.appendChild(i)}return this.container.appendChild(t),this.container}}),t}(),k=function(){function t(t){var e=!(!t.data.title&&!t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){this.container.className="popupAnnotation";var t='[data-annotation-id="'+this.data.parentId+'"]',e=this.layer.querySelector(t);if(!e)return this.container;var n=new C({container:this.container,trigger:e,color:this.data.color,title:this.data.title,contents:this.data.contents}),r=parseFloat(e.style.left),i=parseFloat(e.style.width);return p.setProp("transformOrigin",this.container,-(r+i)+"px -"+e.style.top),this.container.style.left=r+i+"px",this.container.appendChild(n.render()),this.container}}),t}(),C=function(){function t(t){this.container=t.container,this.trigger=t.trigger,this.color=t.color,this.title=t.title,this.contents=t.contents,this.hideWrapper=t.hideWrapper||!1,this.pinned=!1}return t.prototype={render:function(){var t=document.createElement("div");t.className="popupWrapper",this.hideElement=this.hideWrapper?t:this.container,this.hideElement.setAttribute("hidden",!0);var e=document.createElement("div");e.className="popup";var n=this.color;if(n){var r=.7*(255-n[0])+n[0],i=.7*(255-n[1])+n[1],a=.7*(255-n[2])+n[2];e.style.backgroundColor=l.makeCssRgb(0|r,0|i,0|a)}var s=this._formatContents(this.contents),o=document.createElement("h1");return o.textContent=this.title,this.trigger.addEventListener("click",this._toggle.bind(this)),this.trigger.addEventListener("mouseover",this._show.bind(this,!1)),this.trigger.addEventListener("mouseout",this._hide.bind(this,!1)),e.addEventListener("click",this._hide.bind(this,!0)),e.appendChild(o),e.appendChild(s),t.appendChild(e),t},_formatContents:function(t){for(var e=document.createElement("p"),n=t.split(/(?:\r\n?|\n)/),r=0,i=n.length;r<i;++r){var a=n[r];e.appendChild(document.createTextNode(a)),r<i-1&&e.appendChild(document.createElement("br"))}return e},_toggle:function(){this.pinned?this._hide(!0):this._show(!0)},_show:function(t){t&&(this.pinned=!0),this.hideElement.hasAttribute("hidden")&&(this.hideElement.removeAttribute("hidden"),this.container.style.zIndex+=1)},_hide:function(t){t&&(this.pinned=!1),this.hideElement.hasAttribute("hidden")||this.pinned||(this.hideElement.setAttribute("hidden",!0),this.container.style.zIndex-=1)}},t}(),_=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="highlightAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),T=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents) +;m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="underlineAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),P=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="squigglyAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),L=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="strikeoutAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),E=function(){function t(t){m.call(this,t,!0);var e=this.data.file;this.filename=d(e.filename),this.content=e.content,this.linkService.onFileAttachmentAnnotation({id:c(e.filename),filename:e.filename,content:e.content})}return l.inherit(t,m,{render:function(){this.container.className="fileAttachmentAnnotation";var t=document.createElement("div");return t.style.height=this.container.style.height,t.style.width=this.container.style.width,t.addEventListener("dblclick",this._download.bind(this)),this.data.hasPopup||!this.data.title&&!this.data.contents||this._createPopup(this.container,t,this.data),this.container.appendChild(t),this.container},_download:function(){if(!this.downloadManager)return void f("Download cannot be started due to unavailable download manager");this.downloadManager.downloadData(this.content,this.filename,"")}}),t}(),R=function(){return{render:function(t){for(var e=new r,n=0,i=t.annotations.length;n<i;n++){var a=t.annotations[n];if(a){var s=e.create({data:a,layer:t.div,page:t.page,viewport:t.viewport,linkService:t.linkService,downloadManager:t.downloadManager,imageResourcesPath:t.imageResourcesPath||g("imageResourcesPath"),renderInteractiveForms:t.renderInteractiveForms||!1});s.isRenderable&&t.div.appendChild(s.render())}}},update:function(t){for(var e=0,n=t.annotations.length;e<n;e++){var r=t.annotations[e],i=t.div.querySelector('[data-annotation-id="'+r.id+'"]');i&&p.setProp("transform",i,"matrix("+t.viewport.transform.join(",")+")")}t.div.removeAttribute("hidden")}}}();e.AnnotationLayer=R},function(t,e,n){"use strict";function r(t,e,n,r){var a=new V;arguments.length>1&&S("getDocument is called with pdfDataRangeTransport, passwordCallback or progressCallback argument"),e&&(e instanceof J||(e=Object.create(e),e.length=t.length,e.initialData=t.initialData,e.abort||(e.abort=function(){})),t=Object.create(t),t.range=e),a.onPassword=n||null,a.onProgress=r||null;var s;"string"==typeof t?s={url:t}:T(t)?s={data:t}:t instanceof J?s={range:t}:("object"!=typeof t&&x("Invalid parameter in getDocument, need either Uint8Array, string or a parameter object"),t.url||t.data||t.range||x("Invalid parameter object: need either .data, .range or .url"),s=t);var o={},c=null,l=null;for(var h in s)if("url"!==h||"undefined"==typeof window)if("range"!==h)if("worker"!==h)if("data"!==h||s[h]instanceof Uint8Array)o[h]=s[h];else{var u=s[h];"string"==typeof u?o[h]=E(u):"object"!=typeof u||null===u||isNaN(u.length)?T(u)?o[h]=new Uint8Array(u):x("Invalid PDF binary data: either typed array, string or array-like object is expected in the data property."):o[h]=new Uint8Array(u)}else l=s[h];else c=s[h];else o[h]=new URL(s[h],window.location).href;o.rangeChunkSize=o.rangeChunkSize||W,o.disableNativeImageDecoder=!0===o.disableNativeImageDecoder;var f=o.CMapReaderFactory||B;if(!l){var p=j("workerPort");l=p?new Z(null,p):new Z,a._worker=l}var g=a.docId;return l.promise.then(function(){if(a.destroyed)throw new Error("Loading aborted");return i(l,o,c,g).then(function(t){if(a.destroyed)throw new Error("Loading aborted");var e=new d(g,t,l.port),n=new $(e,a,c,f);a._transport=n,e.send("Ready",null)})}).catch(a._capability.reject),a}function i(t,e,n,r){return t.destroyed?Promise.reject(new Error("Worker was destroyed")):(e.disableAutoFetch=j("disableAutoFetch"),e.disableStream=j("disableStream"),e.chunkedViewerLoading=!!n,n&&(e.length=n.length,e.initialData=n.initialData),t.messageHandler.sendWithPromise("GetDocRequest",{docId:r,source:e,disableRange:j("disableRange"),maxImageSize:j("maxImageSize"),disableFontFace:j("disableFontFace"),disableCreateObjectURL:j("disableCreateObjectURL"),postMessageTransfers:j("postMessageTransfers")&&!X,docBaseUrl:e.docBaseUrl,disableNativeImageDecoder:e.disableNativeImageDecoder}).then(function(e){if(t.destroyed)throw new Error("Worker was destroyed");return e}))}var a,s=n(0),o=n(11),c=n(10),l=n(7),h=n(1),u=s.InvalidPDFException,d=s.MessageHandler,f=s.MissingPDFException,p=s.PageViewport,g=s.PasswordException,m=s.StatTimer,A=s.UnexpectedResponseException,v=s.UnknownErrorException,b=s.Util,y=s.createPromiseCapability,x=s.error,S=s.deprecated,w=s.getVerbosityLevel,k=s.info,C=s.isInt,_=s.isArray,T=s.isArrayBuffer,P=s.isSameOrigin,L=s.loadJpegStream,E=s.stringToBytes,R=s.globalScope,I=s.warn,F=o.FontFaceObject,O=o.FontLoader,M=c.CanvasGraphics,D=l.Metadata,N=h.RenderingCancelledException,j=h.getDefaultSetting,U=h.DOMCanvasFactory,B=h.DOMCMapReaderFactory,W=65536,G=!1,X=!1,H="undefined"!=typeof document&&document.currentScript?document.currentScript.src:null,z=null,Y=!1;if("undefined"==typeof __pdfjsdev_webpack__){"undefined"==typeof window?(G=!0,void 0===require.ensure&&(require.ensure=require("node-ensure")),Y=!0):"undefined"!=typeof require&&"function"==typeof require.ensure&&(Y=!0),"undefined"!=typeof requirejs&&requirejs.toUrl&&(a=requirejs.toUrl("pdfjs-dist/build/pdf.worker.js"));var q="undefined"!=typeof requirejs&&requirejs.load;z=Y?function(t){require.ensure([],function(){var e=require("./pdf.worker.js");t(e.WorkerMessageHandler)})}:q?function(t){requirejs(["pdfjs-dist/build/pdf.worker"],function(e){t(e.WorkerMessageHandler)})}:null}var V=function(){function t(){this._capability=y(),this._transport=null,this._worker=null,this.docId="d"+e++,this.destroyed=!1,this.onPassword=null,this.onProgress=null,this.onUnsupportedFeature=null}var e=0;return t.prototype={get promise(){return this._capability.promise},destroy:function(){return this.destroyed=!0,(this._transport?this._transport.destroy():Promise.resolve()).then(function(){this._transport=null,this._worker&&(this._worker.destroy(),this._worker=null)}.bind(this))},then:function(t,e){return this.promise.then.apply(this.promise,arguments)}},t}(),J=function(){function t(t,e){this.length=t,this.initialData=e,this._rangeListeners=[],this._progressListeners=[],this._progressiveReadListeners=[],this._readyCapability=y()}return t.prototype={addRangeListener:function(t){this._rangeListeners.push(t)},addProgressListener:function(t){this._progressListeners.push(t)},addProgressiveReadListener:function(t){this._progressiveReadListeners.push(t)},onDataRange:function(t,e){for(var n=this._rangeListeners,r=0,i=n.length;r<i;++r)n[r](t,e)},onDataProgress:function(t){this._readyCapability.promise.then(function(){for(var e=this._progressListeners,n=0,r=e.length;n<r;++n)e[n](t)}.bind(this))},onDataProgressiveRead:function(t){this._readyCapability.promise.then(function(){for(var e=this._progressiveReadListeners,n=0,r=e.length;n<r;++n)e[n](t)}.bind(this))},transportReady:function(){this._readyCapability.resolve()},requestDataRange:function(t,e){throw new Error("Abstract method PDFDataRangeTransport.requestDataRange")},abort:function(){}},t}(),Q=function(){function t(t,e,n){this.pdfInfo=t,this.transport=e,this.loadingTask=n}return t.prototype={get numPages(){return this.pdfInfo.numPages},get fingerprint(){return this.pdfInfo.fingerprint},getPage:function(t){return this.transport.getPage(t)},getPageIndex:function(t){return this.transport.getPageIndex(t)},getDestinations:function(){return this.transport.getDestinations()},getDestination:function(t){return this.transport.getDestination(t)},getPageLabels:function(){return this.transport.getPageLabels()},getAttachments:function(){return this.transport.getAttachments()},getJavaScript:function(){return this.transport.getJavaScript()},getOutline:function(){return this.transport.getOutline()},getMetadata:function(){return this.transport.getMetadata()},getData:function(){return this.transport.getData()},getDownloadInfo:function(){return this.transport.downloadInfoCapability.promise},getStats:function(){return this.transport.getStats()},cleanup:function(){this.transport.startCleanup()},destroy:function(){return this.loadingTask.destroy()}},t}(),K=function(){function t(t,e,n){this.pageIndex=t,this.pageInfo=e,this.transport=n,this.stats=new m,this.stats.enabled=j("enableStats"),this.commonObjs=n.commonObjs,this.objs=new tt,this.cleanupAfterRender=!1,this.pendingCleanup=!1,this.intentStates=Object.create(null),this.destroyed=!1}return t.prototype={get pageNumber(){return this.pageIndex+1},get rotate(){return this.pageInfo.rotate},get ref(){return this.pageInfo.ref},get userUnit(){return this.pageInfo.userUnit},get view(){return this.pageInfo.view},getViewport:function(t,e){return arguments.length<2&&(e=this.rotate),new p(this.view,t,e,0,0)},getAnnotations:function(t){var e=t&&t.intent||null;return this.annotationsPromise&&this.annotationsIntent===e||(this.annotationsPromise=this.transport.getAnnotations(this.pageIndex,e),this.annotationsIntent=e),this.annotationsPromise},render:function(t){function e(t){var e=s.renderTasks.indexOf(o);e>=0&&s.renderTasks.splice(e,1),l.cleanupAfterRender&&(l.pendingCleanup=!0),l._tryCleanup(),t?o.capability.reject(t):o.capability.resolve(),n.timeEnd("Rendering"),n.timeEnd("Overall")}var n=this.stats;n.time("Overall"),this.pendingCleanup=!1;var r="print"===t.intent?"print":"display",i=!0===t.renderInteractiveForms,a=t.canvasFactory||new U;this.intentStates[r]||(this.intentStates[r]=Object.create(null));var s=this.intentStates[r];s.displayReadyCapability||(s.receivingOperatorList=!0,s.displayReadyCapability=y(),s.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.stats.time("Page Request"),this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageNumber-1,intent:r,renderInteractiveForms:i}));var o=new nt(e,t,this.objs,this.commonObjs,s.operatorList,this.pageNumber,a);o.useRequestAnimationFrame="print"!==r,s.renderTasks||(s.renderTasks=[]),s.renderTasks.push(o);var c=o.task;t.continueCallback&&(S("render is used with continueCallback parameter"),c.onContinue=t.continueCallback);var l=this;return s.displayReadyCapability.promise.then(function(t){if(l.pendingCleanup)return void e();n.time("Rendering"),o.initializeGraphics(t),o.operatorListChanged()},function(t){e(t)}),c},getOperatorList:function(){function t(){if(n.operatorList.lastChunk){n.opListReadCapability.resolve(n.operatorList);var t=n.renderTasks.indexOf(e);t>=0&&n.renderTasks.splice(t,1)}}this.intentStates.oplist||(this.intentStates.oplist=Object.create(null));var e,n=this.intentStates.oplist;return n.opListReadCapability||(e={},e.operatorListChanged=t,n.receivingOperatorList=!0,n.opListReadCapability=y(),n.renderTasks=[],n.renderTasks.push(e),n.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageIndex,intent:"oplist"})),n.opListReadCapability.promise},getTextContent:function(t){return this.transport.messageHandler.sendWithPromise("GetTextContent",{pageIndex:this.pageNumber-1,normalizeWhitespace:!(!t||!0!==t.normalizeWhitespace),combineTextItems:!t||!0!==t.disableCombineTextItems})},_destroy:function(){this.destroyed=!0,this.transport.pageCache[this.pageIndex]=null;var t=[];return Object.keys(this.intentStates).forEach(function(e){if("oplist"!==e){this.intentStates[e].renderTasks.forEach(function(e){var n=e.capability.promise.catch(function(){});t.push(n),e.cancel()})}},this),this.objs.clear(),this.annotationsPromise=null,this.pendingCleanup=!1,Promise.all(t)},destroy:function(){S("page destroy method, use cleanup() instead"),this.cleanup()},cleanup:function(){this.pendingCleanup=!0,this._tryCleanup()},_tryCleanup:function(){this.pendingCleanup&&!Object.keys(this.intentStates).some(function(t){var e=this.intentStates[t];return 0!==e.renderTasks.length||e.receivingOperatorList},this)&&(Object.keys(this.intentStates).forEach(function(t){delete this.intentStates[t]},this),this.objs.clear(),this.annotationsPromise=null,this.pendingCleanup=!1)},_startRenderPage:function(t,e){var n=this.intentStates[e];n.displayReadyCapability&&n.displayReadyCapability.resolve(t)},_renderPageChunk:function(t,e){var n,r,i=this.intentStates[e];for(n=0,r=t.length;n<r;n++)i.operatorList.fnArray.push(t.fnArray[n]),i.operatorList.argsArray.push(t.argsArray[n]);for(i.operatorList.lastChunk=t.lastChunk,n=0;n<i.renderTasks.length;n++)i.renderTasks[n].operatorListChanged();t.lastChunk&&(i.receivingOperatorList=!1,this._tryCleanup())}},t}(),Z=function(){function t(){return void 0!==a?a:j("workerSrc")?j("workerSrc"):H?H.replace(/\.js$/i,".worker.js"):void x("No PDFJS.workerSrc specified")}function e(){return s?s.promise:(s=y(),(z||function(e){b.loadScript(t(),function(){e(window.pdfjsDistBuildPdfWorker.WorkerMessageHandler)})})(s.resolve),s.promise)}function n(t){this._listeners=[],this._defer=t,this._deferred=Promise.resolve(void 0)}function r(t){var e="importScripts('"+t+"');";return URL.createObjectURL(new Blob([e]))}function i(t,e){if(this.name=t,this.destroyed=!1,this._readyCapability=y(),this._port=null,this._webWorker=null,this._messageHandler=null,e)return void this._initializeFromPort(e);this._initialize()}var s,o=0;return n.prototype={postMessage:function(t,e){function n(t){if("object"!=typeof t||null===t)return t;if(r.has(t))return r.get(t);var i,a;if((a=t.buffer)&&T(a)){var s=e&&e.indexOf(a)>=0;return i=t===a?t:s?new t.constructor(a,t.byteOffset,t.byteLength):new t.constructor(t),r.set(t,i),i}i=_(t)?[]:{},r.set(t,i);for(var o in t){for(var c,l=t;!(c=Object.getOwnPropertyDescriptor(l,o));)l=Object.getPrototypeOf(l);void 0!==c.value&&"function"!=typeof c.value&&(i[o]=n(c.value))}return i}if(!this._defer)return void this._listeners.forEach(function(e){e.call(this,{data:t})},this);var r=new WeakMap,i={data:n(t)};this._deferred.then(function(){this._listeners.forEach(function(t){t.call(this,i)},this)}.bind(this))},addEventListener:function(t,e){this._listeners.push(e)},removeEventListener:function(t,e){var n=this._listeners.indexOf(e);this._listeners.splice(n,1)},terminate:function(){this._listeners=[]}},i.prototype={get promise(){return this._readyCapability.promise},get port(){return this._port},get messageHandler(){return this._messageHandler},_initializeFromPort:function(t){this._port=t,this._messageHandler=new d("main","worker",t),this._messageHandler.on("ready",function(){}),this._readyCapability.resolve()},_initialize:function(){if(!G&&!j("disableWorker")&&"undefined"!=typeof Worker){var e=t();try{P(window.location.href,e)||(e=r(new URL(e,window.location).href));var n=new Worker(e),i=new d("main","worker",n),a=function(){n.removeEventListener("error",s),i.destroy(),n.terminate(),this.destroyed?this._readyCapability.reject(new Error("Worker was destroyed")):this._setupFakeWorker()}.bind(this),s=function(t){this._webWorker||a()}.bind(this);n.addEventListener("error",s),i.on("test",function(t){if(n.removeEventListener("error",s),this.destroyed)return void a();t&&t.supportTypedArray?(this._messageHandler=i,this._port=n,this._webWorker=n,t.supportTransfers||(X=!0),this._readyCapability.resolve(),i.send("configure",{verbosity:w()})):(this._setupFakeWorker(),i.destroy(),n.terminate())}.bind(this)),i.on("console_log",function(t){console.log.apply(console,t)}),i.on("console_error",function(t){console.error.apply(console,t)}),i.on("ready",function(t){if(n.removeEventListener("error",s),this.destroyed)return void a();try{o()}catch(t){this._setupFakeWorker()}}.bind(this));var o=function(){var t=j("postMessageTransfers")&&!X,e=new Uint8Array([t?255:0]);try{i.send("test",e,[e.buffer])}catch(t){k("Cannot use postMessage transfers"),e[0]=0,i.send("test",e)}};return void o()}catch(t){k("The worker has been disabled.")}}this._setupFakeWorker()},_setupFakeWorker:function(){G||j("disableWorker")||(I("Setting up fake worker."),G=!0),e().then(function(t){if(this.destroyed)return void this._readyCapability.reject(new Error("Worker was destroyed"));var e=Uint8Array!==Float32Array,r=new n(e);this._port=r;var i="fake"+o++,a=new d(i+"_worker",i,r);t.setup(a,r);var s=new d(i,i+"_worker",r);this._messageHandler=s,this._readyCapability.resolve()}.bind(this))},destroy:function(){this.destroyed=!0,this._webWorker&&(this._webWorker.terminate(),this._webWorker=null),this._port=null,this._messageHandler&&(this._messageHandler.destroy(),this._messageHandler=null)}},i}(),$=function(){function t(t,e,n,r){this.messageHandler=t,this.loadingTask=e,this.pdfDataRangeTransport=n,this.commonObjs=new tt,this.fontLoader=new O(e.docId),this.CMapReaderFactory=new r({baseUrl:j("cMapUrl"),isCompressed:j("cMapPacked")}),this.destroyed=!1,this.destroyCapability=null,this._passwordCapability=null,this.pageCache=[],this.pagePromises=[],this.downloadInfoCapability=y(),this.setupMessageHandler()}return t.prototype={destroy:function(){if(this.destroyCapability)return this.destroyCapability.promise;this.destroyed=!0,this.destroyCapability=y(),this._passwordCapability&&this._passwordCapability.reject(new Error("Worker was destroyed during onPassword callback"));var t=[];this.pageCache.forEach(function(e){e&&t.push(e._destroy())}),this.pageCache=[],this.pagePromises=[];var e=this,n=this.messageHandler.sendWithPromise("Terminate",null);return t.push(n),Promise.all(t).then(function(){e.fontLoader.clear(),e.pdfDataRangeTransport&&(e.pdfDataRangeTransport.abort(),e.pdfDataRangeTransport=null),e.messageHandler&&(e.messageHandler.destroy(),e.messageHandler=null),e.destroyCapability.resolve()},this.destroyCapability.reject),this.destroyCapability.promise},setupMessageHandler:function(){var t=this.messageHandler,e=this.loadingTask,n=this.pdfDataRangeTransport;n&&(n.addRangeListener(function(e,n){t.send("OnDataRange",{begin:e,chunk:n})}),n.addProgressListener(function(e){t.send("OnDataProgress",{loaded:e})}),n.addProgressiveReadListener(function(e){t.send("OnDataRange",{chunk:e})}),t.on("RequestDataRange",function(t){n.requestDataRange(t.begin,t.end)},this)),t.on("GetDoc",function(t){var e=t.pdfInfo;this.numPages=t.pdfInfo.numPages;var n=this.loadingTask,r=new Q(e,this,n);this.pdfDocument=r,n._capability.resolve(r)},this),t.on("PasswordRequest",function(t){if(this._passwordCapability=y(),e.onPassword){var n=function(t){this._passwordCapability.resolve({password:t})}.bind(this);e.onPassword(n,t.code)}else this._passwordCapability.reject(new g(t.message,t.code));return this._passwordCapability.promise},this),t.on("PasswordException",function(t){e._capability.reject(new g(t.message,t.code))},this),t.on("InvalidPDF",function(t){this.loadingTask._capability.reject(new u(t.message))},this),t.on("MissingPDF",function(t){this.loadingTask._capability.reject(new f(t.message))},this),t.on("UnexpectedResponse",function(t){this.loadingTask._capability.reject(new A(t.message,t.status))},this),t.on("UnknownError",function(t){this.loadingTask._capability.reject(new v(t.message,t.details))},this),t.on("DataLoaded",function(t){this.downloadInfoCapability.resolve(t)},this),t.on("PDFManagerReady",function(t){this.pdfDataRangeTransport&&this.pdfDataRangeTransport.transportReady()},this),t.on("StartRenderPage",function(t){if(!this.destroyed){var e=this.pageCache[t.pageIndex];e.stats.timeEnd("Page Request"),e._startRenderPage(t.transparency,t.intent)}},this),t.on("RenderPageChunk",function(t){if(!this.destroyed){this.pageCache[t.pageIndex]._renderPageChunk(t.operatorList,t.intent)}},this),t.on("commonobj",function(t){if(!this.destroyed){var e=t[0],n=t[1];if(!this.commonObjs.hasData(e))switch(n){case"Font":var r=t[2];if("error"in r){var i=r.error;I("Error during font loading: "+i),this.commonObjs.resolve(e,i);break}var a=null;j("pdfBug")&&R.FontInspector&&R.FontInspector.enabled&&(a={registerFont:function(t,e){R.FontInspector.fontAdded(t,e)}});var s=new F(r,{isEvalSuported:j("isEvalSupported"),disableFontFace:j("disableFontFace"),fontRegistry:a});this.fontLoader.bind([s],function(t){this.commonObjs.resolve(e,s)}.bind(this));break;case"FontPath":this.commonObjs.resolve(e,t[2]);break;default:x("Got unknown common object type "+n)}}},this),t.on("obj",function(t){if(!this.destroyed){var e,n=t[0],r=t[1],i=t[2],a=this.pageCache[r];if(!a.objs.hasData(n))switch(i){case"JpegStream":e=t[3],L(n,e,a.objs);break;case"Image":e=t[3],a.objs.resolve(n,e);e&&"data"in e&&e.data.length>8e6&&(a.cleanupAfterRender=!0);break;default:x("Got unknown object type "+i)}}},this),t.on("DocProgress",function(t){if(!this.destroyed){var e=this.loadingTask;e.onProgress&&e.onProgress({loaded:t.loaded,total:t.total})}},this),t.on("PageError",function(t){if(!this.destroyed){var e=this.pageCache[t.pageNum-1],n=e.intentStates[t.intent];if(n.displayReadyCapability?n.displayReadyCapability.reject(t.error):x(t.error),n.operatorList){n.operatorList.lastChunk=!0;for(var r=0;r<n.renderTasks.length;r++)n.renderTasks[r].operatorListChanged()}}},this),t.on("UnsupportedFeature",function(t){if(!this.destroyed){var e=t.featureId,n=this.loadingTask;n.onUnsupportedFeature&&n.onUnsupportedFeature(e),rt.notify(e)}},this),t.on("JpegDecode",function(t){if(this.destroyed)return Promise.reject(new Error("Worker was destroyed"));if("undefined"==typeof document)return Promise.reject(new Error('"document" is not defined.'));var e=t[0],n=t[1];return 3!==n&&1!==n?Promise.reject(new Error("Only 3 components or 1 component can be returned")):new Promise(function(t,r){var i=new Image;i.onload=function(){var e=i.width,r=i.height,a=e*r,s=4*a,o=new Uint8Array(a*n),c=document.createElement("canvas");c.width=e,c.height=r;var l=c.getContext("2d");l.drawImage(i,0,0);var h,u,d=l.getImageData(0,0,e,r).data;if(3===n)for(h=0,u=0;h<s;h+=4,u+=3)o[u]=d[h],o[u+1]=d[h+1],o[u+2]=d[h+2];else if(1===n)for(h=0,u=0;h<s;h+=4,u++)o[u]=d[h];t({data:o,width:e,height:r})},i.onerror=function(){r(new Error("JpegDecode failed to load image"))},i.src=e})},this),t.on("FetchBuiltInCMap",function(t){return this.destroyed?Promise.reject(new Error("Worker was destroyed")):this.CMapReaderFactory.fetch({name:t.name})},this)},getData:function(){return this.messageHandler.sendWithPromise("GetData",null)},getPage:function(t,e){if(!C(t)||t<=0||t>this.numPages)return Promise.reject(new Error("Invalid page request"));var n=t-1;if(n in this.pagePromises)return this.pagePromises[n];var r=this.messageHandler.sendWithPromise("GetPage",{pageIndex:n}).then(function(t){if(this.destroyed)throw new Error("Transport destroyed");var e=new K(n,t,this);return this.pageCache[n]=e,e}.bind(this));return this.pagePromises[n]=r,r},getPageIndex:function(t){return this.messageHandler.sendWithPromise("GetPageIndex",{ref:t}).catch(function(t){return Promise.reject(new Error(t))})},getAnnotations:function(t,e){return this.messageHandler.sendWithPromise("GetAnnotations",{pageIndex:t,intent:e})},getDestinations:function(){return this.messageHandler.sendWithPromise("GetDestinations",null)},getDestination:function(t){return this.messageHandler.sendWithPromise("GetDestination",{id:t})},getPageLabels:function(){return this.messageHandler.sendWithPromise("GetPageLabels",null)},getAttachments:function(){return this.messageHandler.sendWithPromise("GetAttachments",null)},getJavaScript:function(){return this.messageHandler.sendWithPromise("GetJavaScript",null)},getOutline:function(){return this.messageHandler.sendWithPromise("GetOutline",null)},getMetadata:function(){return this.messageHandler.sendWithPromise("GetMetadata",null).then(function(t){return{info:t[0],metadata:t[1]?new D(t[1]):null}})},getStats:function(){return this.messageHandler.sendWithPromise("GetStats",null)},startCleanup:function(){this.messageHandler.sendWithPromise("Cleanup",null).then(function(){for(var t=0,e=this.pageCache.length;t<e;t++){var n=this.pageCache[t];n&&n.cleanup()}this.commonObjs.clear(),this.fontLoader.clear()}.bind(this))}},t}(),tt=function(){function t(){this.objs=Object.create(null)}return t.prototype={ensureObj:function(t){if(this.objs[t])return this.objs[t];var e={capability:y(),data:null,resolved:!1};return this.objs[t]=e,e},get:function(t,e){if(e)return this.ensureObj(t).capability.promise.then(e),null;var n=this.objs[t];return n&&n.resolved||x("Requesting object that isn't resolved yet "+t),n.data},resolve:function(t,e){var n=this.ensureObj(t);n.resolved=!0,n.data=e,n.capability.resolve(e)},isResolved:function(t){var e=this.objs;return!!e[t]&&e[t].resolved},hasData:function(t){return this.isResolved(t)},getData:function(t){var e=this.objs;return e[t]&&e[t].resolved?e[t].data:null},clear:function(){this.objs=Object.create(null)}},t}(),et=function(){function t(t){this._internalRenderTask=t,this.onContinue=null}return t.prototype={get promise(){return this._internalRenderTask.capability.promise},cancel:function(){this._internalRenderTask.cancel()},then:function(t,e){return this.promise.then.apply(this.promise,arguments)}},t}(),nt=function(){function t(t,e,n,r,i,a,s){this.callback=t,this.params=e,this.objs=n,this.commonObjs=r,this.operatorListIdx=null,this.operatorList=i,this.pageNumber=a,this.canvasFactory=s,this.running=!1,this.graphicsReadyCallback=null,this.graphicsReady=!1,this.useRequestAnimationFrame=!1,this.cancelled=!1,this.capability=y(),this.task=new et(this),this._continueBound=this._continue.bind(this),this._scheduleNextBound=this._scheduleNext.bind(this),this._nextBound=this._next.bind(this)}return t.prototype={initializeGraphics:function(t){if(!this.cancelled){j("pdfBug")&&R.StepperManager&&R.StepperManager.enabled&&(this.stepper=R.StepperManager.create(this.pageNumber-1),this.stepper.init(this.operatorList),this.stepper.nextBreakPoint=this.stepper.getNextBreakPoint());var e=this.params;this.gfx=new M(e.canvasContext,this.commonObjs,this.objs,this.canvasFactory,e.imageLayer),this.gfx.beginDrawing(e.transform,e.viewport,t),this.operatorListIdx=0,this.graphicsReady=!0,this.graphicsReadyCallback&&this.graphicsReadyCallback()}},cancel:function(){this.running=!1,this.cancelled=!0,j("pdfjsNext")?this.callback(new N("Rendering cancelled, page "+this.pageNumber,"canvas")):this.callback("cancelled")},operatorListChanged:function(){if(!this.graphicsReady)return void(this.graphicsReadyCallback||(this.graphicsReadyCallback=this._continueBound));this.stepper&&this.stepper.updateOperatorList(this.operatorList),this.running||this._continue()},_continue:function(){this.running=!0,this.cancelled||(this.task.onContinue?this.task.onContinue(this._scheduleNextBound):this._scheduleNext())},_scheduleNext:function(){this.useRequestAnimationFrame&&"undefined"!=typeof window?window.requestAnimationFrame(this._nextBound):Promise.resolve(void 0).then(this._nextBound)},_next:function(){this.cancelled||(this.operatorListIdx=this.gfx.executeOperatorList(this.operatorList,this.operatorListIdx,this._continueBound,this.stepper),this.operatorListIdx===this.operatorList.argsArray.length&&(this.running=!1,this.operatorList.lastChunk&&(this.gfx.endDrawing(),this.callback())))}},t}(),rt=function(){var t=[];return{listen:function(e){S("Global UnsupportedManager.listen is used: use PDFDocumentLoadingTask.onUnsupportedFeature instead"),t.push(e)},notify:function(e){for(var n=0,r=t.length;n<r;n++)t[n](e)}}}();e.version="1.8.172",e.build="8ff1fbe7",e.getDocument=r,e.PDFDataRangeTransport=J,e.PDFWorker=Z,e.PDFDocumentProxy=Q,e.PDFPageProxy=K,e._UnsupportedManager=rt},function(t,e,n){"use strict";var r=n(0),i=r.FONT_IDENTITY_MATRIX,a=r.IDENTITY_MATRIX,s=r.ImageKind,o=r.OPS,c=r.Util,l=r.isNum,h=r.isArray,u=r.warn,d=r.createObjectURL,f={fontStyle:"normal",fontWeight:"normal",fillColor:"#000000"},p=function(){function t(t,e,n){for(var r=-1,i=e;i<n;i++){var a=255&(r^t[i]);r=r>>>8^o[a]}return-1^r}function e(e,n,r,i){var a=i,s=n.length;r[a]=s>>24&255,r[a+1]=s>>16&255,r[a+2]=s>>8&255,r[a+3]=255&s,a+=4,r[a]=255&e.charCodeAt(0),r[a+1]=255&e.charCodeAt(1),r[a+2]=255&e.charCodeAt(2),r[a+3]=255&e.charCodeAt(3),a+=4,r.set(n,a),a+=n.length;var o=t(r,i+4,a);r[a]=o>>24&255,r[a+1]=o>>16&255,r[a+2]=o>>8&255,r[a+3]=255&o}function n(t,e,n){for(var r=1,i=0,a=e;a<n;++a)r=(r+(255&t[a]))%65521,i=(i+r)%65521;return i<<16|r}function r(t,r,o){var c,l,h,u=t.width,f=t.height,p=t.data;switch(r){case s.GRAYSCALE_1BPP:l=0,c=1,h=u+7>>3;break;case s.RGB_24BPP:l=2,c=8,h=3*u;break;case s.RGBA_32BPP:l=6,c=8,h=4*u;break;default:throw new Error("invalid format")}var g,m,A=new Uint8Array((1+h)*f),v=0,b=0;for(g=0;g<f;++g)A[v++]=0,A.set(p.subarray(b,b+h),v),b+=h,v+=h;if(r===s.GRAYSCALE_1BPP)for(v=0,g=0;g<f;g++)for(v++,m=0;m<h;m++)A[v++]^=255;var y=new Uint8Array([u>>24&255,u>>16&255,u>>8&255,255&u,f>>24&255,f>>16&255,f>>8&255,255&f,c,l,0,0,0]),x=A.length,S=Math.ceil(x/65535),w=new Uint8Array(2+x+5*S+4),k=0;w[k++]=120,w[k++]=156;for(var C=0;x>65535;)w[k++]=0,w[k++]=255,w[k++]=255,w[k++]=0,w[k++]=0,w.set(A.subarray(C,C+65535),k),k+=65535,C+=65535,x-=65535;w[k++]=1,w[k++]=255&x,w[k++]=x>>8&255,w[k++]=255&~x,w[k++]=(65535&~x)>>8&255,w.set(A.subarray(C),k),k+=A.length-C;var _=n(A,0,A.length);w[k++]=_>>24&255,w[k++]=_>>16&255,w[k++]=_>>8&255,w[k++]=255&_;var T=i.length+3*a+y.length+w.length,P=new Uint8Array(T),L=0;return P.set(i,L),L+=i.length,e("IHDR",y,P,L),L+=a+y.length,e("IDATA",w,P,L),L+=a+w.length,e("IEND",new Uint8Array(0),P,L),d(P,"image/png",o)}for(var i=new Uint8Array([137,80,78,71,13,10,26,10]),a=12,o=new Int32Array(256),c=0;c<256;c++){for(var l=c,h=0;h<8;h++)l=1&l?3988292384^l>>1&2147483647:l>>1&2147483647;o[c]=l}return function(t,e){return r(t,void 0===t.kind?s.GRAYSCALE_1BPP:t.kind,e)}}(),g=function(){function t(){this.fontSizeScale=1,this.fontWeight=f.fontWeight,this.fontSize=0,this.textMatrix=a,this.fontMatrix=i,this.leading=0,this.x=0,this.y=0,this.lineX=0,this.lineY=0,this.charSpacing=0,this.wordSpacing=0,this.textHScale=1,this.textRise=0,this.fillColor=f.fillColor,this.strokeColor="#000000",this.fillAlpha=1,this.strokeAlpha=1,this.lineWidth=1,this.lineJoin="",this.lineCap="",this.miterLimit=0,this.dashArray=[],this.dashPhase=0,this.dependencies=[],this.activeClipUrl=null,this.clipGroup=null,this.maskId=""}return t.prototype={clone:function(){return Object.create(this)},setCurrentPoint:function(t,e){this.x=t,this.y=e}},t}(),m=function(){function t(t){for(var e=[],n=[],r=t.length,i=0;i<r;i++)"save"!==t[i].fn?"restore"===t[i].fn?e=n.pop():e.push(t[i]):(e.push({fnId:92,fn:"group",items:[]}),n.push(e),e=e[e.length-1].items);return e}function e(t){if(t===(0|t))return t.toString();var e=t.toFixed(10),n=e.length-1;if("0"!==e[n])return e;do{n--}while("0"===e[n]);return e.substr(0,"."===e[n]?n:n+1)}function n(t){if(0===t[4]&&0===t[5]){if(0===t[1]&&0===t[2])return 1===t[0]&&1===t[3]?"":"scale("+e(t[0])+" "+e(t[3])+")";if(t[0]===t[3]&&t[1]===-t[2]){return"rotate("+e(180*Math.acos(t[0])/Math.PI)+")"}}else if(1===t[0]&&0===t[1]&&0===t[2]&&1===t[3])return"translate("+e(t[4])+" "+e(t[5])+")";return"matrix("+e(t[0])+" "+e(t[1])+" "+e(t[2])+" "+e(t[3])+" "+e(t[4])+" "+e(t[5])+")"}function r(t,e,n){this.current=new g,this.transformMatrix=a,this.transformStack=[],this.extraStack=[],this.commonObjs=t,this.objs=e,this.pendingEOFill=!1,this.embedFonts=!1,this.embeddedFonts=Object.create(null),this.cssStyle=null,this.forceDataSchema=!!n}var s="http://www.w3.org/2000/svg",m="http://www.w3.org/1999/xlink",A=["butt","round","square"],v=["miter","round","bevel"],b=0,y=0;return r.prototype={save:function(){this.transformStack.push(this.transformMatrix);var t=this.current;this.extraStack.push(t),this.current=t.clone()},restore:function(){this.transformMatrix=this.transformStack.pop(),this.current=this.extraStack.pop(),this.tgrp=null},group:function(t){this.save(),this.executeOpTree(t),this.restore()},loadDependencies:function(t){ +for(var e=t.fnArray,n=e.length,r=t.argsArray,i=this,a=0;a<n;a++)if(o.dependency===e[a])for(var s=r[a],c=0,l=s.length;c<l;c++){var h,u=s[c],d="g_"===u.substring(0,2);h=d?new Promise(function(t){i.commonObjs.get(u,t)}):new Promise(function(t){i.objs.get(u,t)}),this.current.dependencies.push(h)}return Promise.all(this.current.dependencies)},transform:function(t,e,n,r,i,a){var s=[t,e,n,r,i,a];this.transformMatrix=c.transform(this.transformMatrix,s),this.tgrp=null},getSVG:function(t,e){this.viewport=e;var n=this._initialize(e);return this.loadDependencies(t).then(function(){this.transformMatrix=a;var e=this.convertOpList(t);return this.executeOpTree(e),n}.bind(this))},convertOpList:function(e){var n=e.argsArray,r=e.fnArray,i=r.length,a=[],s=[];for(var c in o)a[o[c]]=c;for(var l=0;l<i;l++){var h=r[l];s.push({fnId:h,fn:a[h],args:n[l]})}return t(s)},executeOpTree:function(t){for(var e=t.length,n=0;n<e;n++){var r=t[n].fn,i=t[n].fnId,a=t[n].args;switch(0|i){case o.beginText:this.beginText();break;case o.setLeading:this.setLeading(a);break;case o.setLeadingMoveText:this.setLeadingMoveText(a[0],a[1]);break;case o.setFont:this.setFont(a);break;case o.showText:case o.showSpacedText:this.showText(a[0]);break;case o.endText:this.endText();break;case o.moveText:this.moveText(a[0],a[1]);break;case o.setCharSpacing:this.setCharSpacing(a[0]);break;case o.setWordSpacing:this.setWordSpacing(a[0]);break;case o.setHScale:this.setHScale(a[0]);break;case o.setTextMatrix:this.setTextMatrix(a[0],a[1],a[2],a[3],a[4],a[5]);break;case o.setLineWidth:this.setLineWidth(a[0]);break;case o.setLineJoin:this.setLineJoin(a[0]);break;case o.setLineCap:this.setLineCap(a[0]);break;case o.setMiterLimit:this.setMiterLimit(a[0]);break;case o.setFillRGBColor:this.setFillRGBColor(a[0],a[1],a[2]);break;case o.setStrokeRGBColor:this.setStrokeRGBColor(a[0],a[1],a[2]);break;case o.setDash:this.setDash(a[0],a[1]);break;case o.setGState:this.setGState(a[0]);break;case o.fill:this.fill();break;case o.eoFill:this.eoFill();break;case o.stroke:this.stroke();break;case o.fillStroke:this.fillStroke();break;case o.eoFillStroke:this.eoFillStroke();break;case o.clip:this.clip("nonzero");break;case o.eoClip:this.clip("evenodd");break;case o.paintSolidColorImageMask:this.paintSolidColorImageMask();break;case o.paintJpegXObject:this.paintJpegXObject(a[0],a[1],a[2]);break;case o.paintImageXObject:this.paintImageXObject(a[0]);break;case o.paintInlineImageXObject:this.paintInlineImageXObject(a[0]);break;case o.paintImageMaskXObject:this.paintImageMaskXObject(a[0]);break;case o.paintFormXObjectBegin:this.paintFormXObjectBegin(a[0],a[1]);break;case o.paintFormXObjectEnd:this.paintFormXObjectEnd();break;case o.closePath:this.closePath();break;case o.closeStroke:this.closeStroke();break;case o.closeFillStroke:this.closeFillStroke();break;case o.nextLine:this.nextLine();break;case o.transform:this.transform(a[0],a[1],a[2],a[3],a[4],a[5]);break;case o.constructPath:this.constructPath(a[0],a[1]);break;case o.endPath:this.endPath();break;case 92:this.group(t[n].items);break;default:u("Unimplemented operator "+r)}}},setWordSpacing:function(t){this.current.wordSpacing=t},setCharSpacing:function(t){this.current.charSpacing=t},nextLine:function(){this.moveText(0,this.current.leading)},setTextMatrix:function(t,n,r,i,a,o){var c=this.current;this.current.textMatrix=this.current.lineMatrix=[t,n,r,i,a,o],this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0,c.xcoords=[],c.tspan=document.createElementNS(s,"svg:tspan"),c.tspan.setAttributeNS(null,"font-family",c.fontFamily),c.tspan.setAttributeNS(null,"font-size",e(c.fontSize)+"px"),c.tspan.setAttributeNS(null,"y",e(-c.y)),c.txtElement=document.createElementNS(s,"svg:text"),c.txtElement.appendChild(c.tspan)},beginText:function(){this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0,this.current.textMatrix=a,this.current.lineMatrix=a,this.current.tspan=document.createElementNS(s,"svg:tspan"),this.current.txtElement=document.createElementNS(s,"svg:text"),this.current.txtgrp=document.createElementNS(s,"svg:g"),this.current.xcoords=[]},moveText:function(t,n){var r=this.current;this.current.x=this.current.lineX+=t,this.current.y=this.current.lineY+=n,r.xcoords=[],r.tspan=document.createElementNS(s,"svg:tspan"),r.tspan.setAttributeNS(null,"font-family",r.fontFamily),r.tspan.setAttributeNS(null,"font-size",e(r.fontSize)+"px"),r.tspan.setAttributeNS(null,"y",e(-r.y))},showText:function(t){var r=this.current,i=r.font,a=r.fontSize;if(0!==a){var s,o=r.charSpacing,c=r.wordSpacing,h=r.fontDirection,u=r.textHScale*h,d=t.length,p=i.vertical,g=a*r.fontMatrix[0],m=0;for(s=0;s<d;++s){var A=t[s];if(null!==A)if(l(A))m+=-A*a*.001;else{r.xcoords.push(r.x+m*u);var v=A.width,b=A.fontChar,y=v*g+o*h;m+=y,r.tspan.textContent+=b}else m+=h*c}p?r.y-=m*u:r.x+=m*u,r.tspan.setAttributeNS(null,"x",r.xcoords.map(e).join(" ")),r.tspan.setAttributeNS(null,"y",e(-r.y)),r.tspan.setAttributeNS(null,"font-family",r.fontFamily),r.tspan.setAttributeNS(null,"font-size",e(r.fontSize)+"px"),r.fontStyle!==f.fontStyle&&r.tspan.setAttributeNS(null,"font-style",r.fontStyle),r.fontWeight!==f.fontWeight&&r.tspan.setAttributeNS(null,"font-weight",r.fontWeight),r.fillColor!==f.fillColor&&r.tspan.setAttributeNS(null,"fill",r.fillColor),r.txtElement.setAttributeNS(null,"transform",n(r.textMatrix)+" scale(1, -1)"),r.txtElement.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),r.txtElement.appendChild(r.tspan),r.txtgrp.appendChild(r.txtElement),this._ensureTransformGroup().appendChild(r.txtElement)}},setLeadingMoveText:function(t,e){this.setLeading(-e),this.moveText(t,e)},addFontStyle:function(t){this.cssStyle||(this.cssStyle=document.createElementNS(s,"svg:style"),this.cssStyle.setAttributeNS(null,"type","text/css"),this.defs.appendChild(this.cssStyle));var e=d(t.data,t.mimetype,this.forceDataSchema);this.cssStyle.textContent+='@font-face { font-family: "'+t.loadedName+'"; src: url('+e+"); }\n"},setFont:function(t){var n=this.current,r=this.commonObjs.get(t[0]),a=t[1];this.current.font=r,this.embedFonts&&r.data&&!this.embeddedFonts[r.loadedName]&&(this.addFontStyle(r),this.embeddedFonts[r.loadedName]=r),n.fontMatrix=r.fontMatrix?r.fontMatrix:i;var o=r.black?r.bold?"bolder":"bold":r.bold?"bold":"normal",c=r.italic?"italic":"normal";a<0?(a=-a,n.fontDirection=-1):n.fontDirection=1,n.fontSize=a,n.fontFamily=r.loadedName,n.fontWeight=o,n.fontStyle=c,n.tspan=document.createElementNS(s,"svg:tspan"),n.tspan.setAttributeNS(null,"y",e(-n.y)),n.xcoords=[]},endText:function(){},setLineWidth:function(t){this.current.lineWidth=t},setLineCap:function(t){this.current.lineCap=A[t]},setLineJoin:function(t){this.current.lineJoin=v[t]},setMiterLimit:function(t){this.current.miterLimit=t},setStrokeRGBColor:function(t,e,n){var r=c.makeCssRgb(t,e,n);this.current.strokeColor=r},setFillRGBColor:function(t,e,n){var r=c.makeCssRgb(t,e,n);this.current.fillColor=r,this.current.tspan=document.createElementNS(s,"svg:tspan"),this.current.xcoords=[]},setDash:function(t,e){this.current.dashArray=t,this.current.dashPhase=e},constructPath:function(t,n){var r=this.current,i=r.x,a=r.y;r.path=document.createElementNS(s,"svg:path");for(var c=[],l=t.length,h=0,u=0;h<l;h++)switch(0|t[h]){case o.rectangle:i=n[u++],a=n[u++];var d=n[u++],f=n[u++],p=i+d,g=a+f;c.push("M",e(i),e(a),"L",e(p),e(a),"L",e(p),e(g),"L",e(i),e(g),"Z");break;case o.moveTo:i=n[u++],a=n[u++],c.push("M",e(i),e(a));break;case o.lineTo:i=n[u++],a=n[u++],c.push("L",e(i),e(a));break;case o.curveTo:i=n[u+4],a=n[u+5],c.push("C",e(n[u]),e(n[u+1]),e(n[u+2]),e(n[u+3]),e(i),e(a)),u+=6;break;case o.curveTo2:i=n[u+2],a=n[u+3],c.push("C",e(i),e(a),e(n[u]),e(n[u+1]),e(n[u+2]),e(n[u+3])),u+=4;break;case o.curveTo3:i=n[u+2],a=n[u+3],c.push("C",e(n[u]),e(n[u+1]),e(i),e(a),e(i),e(a)),u+=4;break;case o.closePath:c.push("Z")}r.path.setAttributeNS(null,"d",c.join(" ")),r.path.setAttributeNS(null,"stroke-miterlimit",e(r.miterLimit)),r.path.setAttributeNS(null,"stroke-linecap",r.lineCap),r.path.setAttributeNS(null,"stroke-linejoin",r.lineJoin),r.path.setAttributeNS(null,"stroke-width",e(r.lineWidth)+"px"),r.path.setAttributeNS(null,"stroke-dasharray",r.dashArray.map(e).join(" ")),r.path.setAttributeNS(null,"stroke-dashoffset",e(r.dashPhase)+"px"),r.path.setAttributeNS(null,"fill","none"),this._ensureTransformGroup().appendChild(r.path),r.element=r.path,r.setCurrentPoint(i,a)},endPath:function(){},clip:function(t){var e=this.current,r="clippath"+b;b++;var i=document.createElementNS(s,"svg:clipPath");i.setAttributeNS(null,"id",r),i.setAttributeNS(null,"transform",n(this.transformMatrix));var a=e.element.cloneNode();"evenodd"===t?a.setAttributeNS(null,"clip-rule","evenodd"):a.setAttributeNS(null,"clip-rule","nonzero"),i.appendChild(a),this.defs.appendChild(i),e.activeClipUrl&&(e.clipGroup=null,this.extraStack.forEach(function(t){t.clipGroup=null})),e.activeClipUrl="url(#"+r+")",this.tgrp=null},closePath:function(){var t=this.current,e=t.path.getAttributeNS(null,"d");e+="Z",t.path.setAttributeNS(null,"d",e)},setLeading:function(t){this.current.leading=-t},setTextRise:function(t){this.current.textRise=t},setHScale:function(t){this.current.textHScale=t/100},setGState:function(t){for(var e=0,n=t.length;e<n;e++){var r=t[e],i=r[0],a=r[1];switch(i){case"LW":this.setLineWidth(a);break;case"LC":this.setLineCap(a);break;case"LJ":this.setLineJoin(a);break;case"ML":this.setMiterLimit(a);break;case"D":this.setDash(a[0],a[1]);break;case"Font":this.setFont(a);break;default:u("Unimplemented graphic state "+i)}}},fill:function(){var t=this.current;t.element.setAttributeNS(null,"fill",t.fillColor)},stroke:function(){var t=this.current;t.element.setAttributeNS(null,"stroke",t.strokeColor),t.element.setAttributeNS(null,"fill","none")},eoFill:function(){var t=this.current;t.element.setAttributeNS(null,"fill",t.fillColor),t.element.setAttributeNS(null,"fill-rule","evenodd")},fillStroke:function(){this.stroke(),this.fill()},eoFillStroke:function(){this.current.element.setAttributeNS(null,"fill-rule","evenodd"),this.fillStroke()},closeStroke:function(){this.closePath(),this.stroke()},closeFillStroke:function(){this.closePath(),this.fillStroke()},paintSolidColorImageMask:function(){var t=this.current,e=document.createElementNS(s,"svg:rect");e.setAttributeNS(null,"x","0"),e.setAttributeNS(null,"y","0"),e.setAttributeNS(null,"width","1px"),e.setAttributeNS(null,"height","1px"),e.setAttributeNS(null,"fill",t.fillColor),this._ensureTransformGroup().appendChild(e)},paintJpegXObject:function(t,n,r){var i=this.objs.get(t),a=document.createElementNS(s,"svg:image");a.setAttributeNS(m,"xlink:href",i.src),a.setAttributeNS(null,"width",i.width+"px"),a.setAttributeNS(null,"height",i.height+"px"),a.setAttributeNS(null,"x","0"),a.setAttributeNS(null,"y",e(-r)),a.setAttributeNS(null,"transform","scale("+e(1/n)+" "+e(-1/r)+")"),this._ensureTransformGroup().appendChild(a)},paintImageXObject:function(t){var e=this.objs.get(t);if(!e)return void u("Dependent image isn't ready yet");this.paintInlineImageXObject(e)},paintInlineImageXObject:function(t,n){var r=t.width,i=t.height,a=p(t,this.forceDataSchema),o=document.createElementNS(s,"svg:rect");o.setAttributeNS(null,"x","0"),o.setAttributeNS(null,"y","0"),o.setAttributeNS(null,"width",e(r)),o.setAttributeNS(null,"height",e(i)),this.current.element=o,this.clip("nonzero");var c=document.createElementNS(s,"svg:image");c.setAttributeNS(m,"xlink:href",a),c.setAttributeNS(null,"x","0"),c.setAttributeNS(null,"y",e(-i)),c.setAttributeNS(null,"width",e(r)+"px"),c.setAttributeNS(null,"height",e(i)+"px"),c.setAttributeNS(null,"transform","scale("+e(1/r)+" "+e(-1/i)+")"),n?n.appendChild(c):this._ensureTransformGroup().appendChild(c)},paintImageMaskXObject:function(t){var n=this.current,r=t.width,i=t.height,a=n.fillColor;n.maskId="mask"+y++;var o=document.createElementNS(s,"svg:mask");o.setAttributeNS(null,"id",n.maskId);var c=document.createElementNS(s,"svg:rect");c.setAttributeNS(null,"x","0"),c.setAttributeNS(null,"y","0"),c.setAttributeNS(null,"width",e(r)),c.setAttributeNS(null,"height",e(i)),c.setAttributeNS(null,"fill",a),c.setAttributeNS(null,"mask","url(#"+n.maskId+")"),this.defs.appendChild(o),this._ensureTransformGroup().appendChild(c),this.paintInlineImageXObject(t,o)},paintFormXObjectBegin:function(t,n){if(h(t)&&6===t.length&&this.transform(t[0],t[1],t[2],t[3],t[4],t[5]),h(n)&&4===n.length){var r=n[2]-n[0],i=n[3]-n[1],a=document.createElementNS(s,"svg:rect");a.setAttributeNS(null,"x",n[0]),a.setAttributeNS(null,"y",n[1]),a.setAttributeNS(null,"width",e(r)),a.setAttributeNS(null,"height",e(i)),this.current.element=a,this.clip("nonzero"),this.endPath()}},paintFormXObjectEnd:function(){},_initialize:function(t){var e=document.createElementNS(s,"svg:svg");e.setAttributeNS(null,"version","1.1"),e.setAttributeNS(null,"width",t.width+"px"),e.setAttributeNS(null,"height",t.height+"px"),e.setAttributeNS(null,"preserveAspectRatio","none"),e.setAttributeNS(null,"viewBox","0 0 "+t.width+" "+t.height);var r=document.createElementNS(s,"svg:defs");e.appendChild(r),this.defs=r;var i=document.createElementNS(s,"svg:g");return i.setAttributeNS(null,"transform",n(t.transform)),e.appendChild(i),this.svg=i,e},_ensureClipGroup:function(){if(!this.current.clipGroup){var t=document.createElementNS(s,"svg:g");t.setAttributeNS(null,"clip-path",this.current.activeClipUrl),this.svg.appendChild(t),this.current.clipGroup=t}return this.current.clipGroup},_ensureTransformGroup:function(){return this.tgrp||(this.tgrp=document.createElementNS(s,"svg:g"),this.tgrp.setAttributeNS(null,"transform",n(this.transformMatrix)),this.current.activeClipUrl?this._ensureClipGroup().appendChild(this.tgrp):this.svg.appendChild(this.tgrp)),this.tgrp}},r}();e.SVGGraphics=m},function(t,e,n){"use strict";var r=n(0),i=n(1),a=r.Util,s=r.createPromiseCapability,o=i.CustomStyle,c=i.getDefaultSetting,l=function(){function t(t){return!f.test(t)}function e(e,n,r){var i=document.createElement("div"),s={style:null,angle:0,canvasWidth:0,isWhitespace:!1,originalTransform:null,paddingBottom:0,paddingLeft:0,paddingRight:0,paddingTop:0,scale:1};if(e._textDivs.push(i),t(n.str))return s.isWhitespace=!0,void e._textDivProperties.set(i,s);var o=a.transform(e._viewport.transform,n.transform),l=Math.atan2(o[1],o[0]),h=r[n.fontName];h.vertical&&(l+=Math.PI/2);var u=Math.sqrt(o[2]*o[2]+o[3]*o[3]),d=u;h.ascent?d=h.ascent*d:h.descent&&(d=(1+h.descent)*d);var f,g;if(0===l?(f=o[4],g=o[5]-d):(f=o[4]+d*Math.sin(l),g=o[5]-d*Math.cos(l)),p[1]=f,p[3]=g,p[5]=u,p[7]=h.fontFamily,s.style=p.join(""),i.setAttribute("style",s.style),i.textContent=n.str,c("pdfBug")&&(i.dataset.fontName=n.fontName),0!==l&&(s.angle=l*(180/Math.PI)),n.str.length>1&&(h.vertical?s.canvasWidth=n.height*e._viewport.scale:s.canvasWidth=n.width*e._viewport.scale),e._textDivProperties.set(i,s),e._enhanceTextSelection){var m=1,A=0;0!==l&&(m=Math.cos(l),A=Math.sin(l));var v,b,y=(h.vertical?n.height:n.width)*e._viewport.scale,x=u;0!==l?(v=[m,A,-A,m,f,g],b=a.getAxialAlignedBoundingBox([0,0,y,x],v)):b=[f,g,f+y,g+x],e._bounds.push({left:b[0],top:b[1],right:b[2],bottom:b[3],div:i,size:[y,x],m:v})}}function n(t){if(!t._canceled){var e=t._container,n=t._textDivs,r=t._capability,i=n.length;if(i>d)return t._renderingDone=!0,void r.resolve();var a=document.createElement("canvas");a.mozOpaque=!0;for(var s,c,l=a.getContext("2d",{alpha:!1}),h=0;h<i;h++){var u=n[h],f=t._textDivProperties.get(u);if(!f.isWhitespace){var p=u.style.fontSize,g=u.style.fontFamily;p===s&&g===c||(l.font=p+" "+g,s=p,c=g);var m=l.measureText(u.textContent).width;e.appendChild(u);var A="";0!==f.canvasWidth&&m>0&&(f.scale=f.canvasWidth/m,A="scaleX("+f.scale+")"),0!==f.angle&&(A="rotate("+f.angle+"deg) "+A),""!==A&&(f.originalTransform=A,o.setProp("transform",u,A)),t._textDivProperties.set(u,f)}}t._renderingDone=!0,r.resolve()}}function r(t){for(var e=t._bounds,n=t._viewport,r=i(n.width,n.height,e),s=0;s<r.length;s++){var o=e[s].div,c=t._textDivProperties.get(o);if(0!==c.angle){var l=r[s],h=e[s],u=h.m,d=u[0],f=u[1],p=[[0,0],[0,h.size[1]],[h.size[0],0],h.size],g=new Float64Array(64);p.forEach(function(t,e){var n=a.applyTransform(t,u);g[e+0]=d&&(l.left-n[0])/d,g[e+4]=f&&(l.top-n[1])/f,g[e+8]=d&&(l.right-n[0])/d,g[e+12]=f&&(l.bottom-n[1])/f,g[e+16]=f&&(l.left-n[0])/-f,g[e+20]=d&&(l.top-n[1])/d,g[e+24]=f&&(l.right-n[0])/-f,g[e+28]=d&&(l.bottom-n[1])/d,g[e+32]=d&&(l.left-n[0])/-d,g[e+36]=f&&(l.top-n[1])/-f,g[e+40]=d&&(l.right-n[0])/-d,g[e+44]=f&&(l.bottom-n[1])/-f,g[e+48]=f&&(l.left-n[0])/f,g[e+52]=d&&(l.top-n[1])/-d,g[e+56]=f&&(l.right-n[0])/f,g[e+60]=d&&(l.bottom-n[1])/-d});var m=function(t,e,n){for(var r=0,i=0;i<n;i++){var a=t[e++];a>0&&(r=r?Math.min(a,r):a)}return r},A=1+Math.min(Math.abs(d),Math.abs(f));c.paddingLeft=m(g,32,16)/A,c.paddingTop=m(g,48,16)/A,c.paddingRight=m(g,0,16)/A,c.paddingBottom=m(g,16,16)/A,t._textDivProperties.set(o,c)}else c.paddingLeft=e[s].left-r[s].left,c.paddingTop=e[s].top-r[s].top,c.paddingRight=r[s].right-e[s].right,c.paddingBottom=r[s].bottom-e[s].bottom,t._textDivProperties.set(o,c)}}function i(t,e,n){var r=n.map(function(t,e){return{x1:t.left,y1:t.top,x2:t.right,y2:t.bottom,index:e,x1New:void 0,x2New:void 0}});l(t,r);var i=new Array(n.length);return r.forEach(function(t){var e=t.index;i[e]={left:t.x1New,top:0,right:t.x2New,bottom:0}}),n.map(function(e,n){var a=i[n],s=r[n];s.x1=e.top,s.y1=t-a.right,s.x2=e.bottom,s.y2=t-a.left,s.index=n,s.x1New=void 0,s.x2New=void 0}),l(e,r),r.forEach(function(t){var e=t.index;i[e].top=t.x1New,i[e].bottom=t.x2New}),i}function l(t,e){e.sort(function(t,e){return t.x1-e.x1||t.index-e.index});var n={x1:-1/0,y1:-1/0,x2:0,y2:1/0,index:-1,x1New:0,x2New:0},r=[{start:-1/0,end:1/0,boundary:n}];e.forEach(function(t){for(var e=0;e<r.length&&r[e].end<=t.y1;)e++;for(var n=r.length-1;n>=0&&r[n].start>=t.y2;)n--;var i,a,s,o,c=-1/0;for(s=e;s<=n;s++){i=r[s],a=i.boundary;var l;l=a.x2>t.x1?a.index>t.index?a.x1New:t.x1:void 0===a.x2New?(a.x2+t.x1)/2:a.x2New,l>c&&(c=l)}for(t.x1New=c,s=e;s<=n;s++)i=r[s],a=i.boundary,void 0===a.x2New?a.x2>t.x1?a.index>t.index&&(a.x2New=a.x2):a.x2New=c:a.x2New>c&&(a.x2New=Math.max(c,a.x2));var h=[],u=null;for(s=e;s<=n;s++){i=r[s],a=i.boundary;var d=a.x2>t.x2?a:t;u===d?h[h.length-1].end=i.end:(h.push({start:i.start,end:i.end,boundary:d}),u=d)}for(r[e].start<t.y1&&(h[0].start=t.y1,h.unshift({start:r[e].start,end:t.y1,boundary:r[e].boundary})),t.y2<r[n].end&&(h[h.length-1].end=t.y2,h.push({start:t.y2,end:r[n].end,boundary:r[n].boundary})),s=e;s<=n;s++)if(i=r[s],a=i.boundary,void 0===a.x2New){var f=!1;for(o=e-1;!f&&o>=0&&r[o].start>=a.y1;o--)f=r[o].boundary===a;for(o=n+1;!f&&o<r.length&&r[o].end<=a.y2;o++)f=r[o].boundary===a;for(o=0;!f&&o<h.length;o++)f=h[o].boundary===a;f||(a.x2New=c)}Array.prototype.splice.apply(r,[e,n-e+1].concat(h))}),r.forEach(function(e){var n=e.boundary;void 0===n.x2New&&(n.x2New=Math.max(t,n.x2))})}function h(t,e,n,r,i){this._textContent=t,this._container=e,this._viewport=n,this._textDivs=r||[],this._textDivProperties=new WeakMap,this._renderingDone=!1,this._canceled=!1,this._capability=s(),this._renderTimer=null,this._bounds=[],this._enhanceTextSelection=!!i}function u(t){var e=new h(t.textContent,t.container,t.viewport,t.textDivs,t.enhanceTextSelection);return e._render(t.timeout),e}var d=1e5,f=/\S/,p=["left: ",0,"px; top: ",0,"px; font-size: ",0,"px; font-family: ","",";"];return h.prototype={get promise(){return this._capability.promise},cancel:function(){this._canceled=!0,null!==this._renderTimer&&(clearTimeout(this._renderTimer),this._renderTimer=null),this._capability.reject("canceled")},_render:function(t){for(var r=this._textContent.items,i=this._textContent.styles,a=0,s=r.length;a<s;a++)e(this,r[a],i);if(t){var o=this;this._renderTimer=setTimeout(function(){n(o),o._renderTimer=null},t)}else n(this)},expandTextDivs:function(t){if(this._enhanceTextSelection&&this._renderingDone){null!==this._bounds&&(r(this),this._bounds=null);for(var e=0,n=this._textDivs.length;e<n;e++){var i=this._textDivs[e],a=this._textDivProperties.get(i);if(!a.isWhitespace)if(t){var s="",c="";1!==a.scale&&(s="scaleX("+a.scale+")"),0!==a.angle&&(s="rotate("+a.angle+"deg) "+s),0!==a.paddingLeft&&(c+=" padding-left: "+a.paddingLeft/a.scale+"px;",s+=" translateX("+-a.paddingLeft/a.scale+"px)"),0!==a.paddingTop&&(c+=" padding-top: "+a.paddingTop+"px;",s+=" translateY("+-a.paddingTop+"px)"),0!==a.paddingRight&&(c+=" padding-right: "+a.paddingRight/a.scale+"px;"),0!==a.paddingBottom&&(c+=" padding-bottom: "+a.paddingBottom+"px;"),""!==c&&i.setAttribute("style",a.style+c),""!==s&&o.setProp("transform",i,s)}else i.style.padding=0,o.setProp("transform",i,a.originalTransform||"")}}}},u}();e.renderTextLayer=l},function(t,e,n){"use strict";var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,e,n){"use strict";function r(t){return t.replace(/>\\376\\377([^<]+)/g,function(t,e){for(var n=e.replace(/\\([0-3])([0-7])([0-7])/g,function(t,e,n,r){return String.fromCharCode(64*e+8*n+1*r)}),r="",i=0;i<n.length;i+=2){var a=256*n.charCodeAt(i)+n.charCodeAt(i+1);r+=a>=32&&a<127&&60!==a&&62!==a&&38!==a?String.fromCharCode(a):"&#x"+(65536+a).toString(16).substring(1)+";"}return">"+r})}function i(t){if("string"==typeof t){t=r(t);t=(new DOMParser).parseFromString(t,"application/xml")}else t instanceof Document||s("Metadata: Invalid metadata object");this.metaDocument=t,this.metadata=Object.create(null),this.parse()}var a=n(0),s=a.error;i.prototype={parse:function(){var t=this.metaDocument,e=t.documentElement;if("rdf:rdf"!==e.nodeName.toLowerCase())for(e=e.firstChild;e&&"rdf:rdf"!==e.nodeName.toLowerCase();)e=e.nextSibling;var n=e?e.nodeName.toLowerCase():null;if(e&&"rdf:rdf"===n&&e.hasChildNodes()){var r,i,a,s,o,c,l,h=e.childNodes;for(s=0,c=h.length;s<c;s++)if(r=h[s],"rdf:description"===r.nodeName.toLowerCase())for(o=0,l=r.childNodes.length;o<l;o++)"#text"!==r.childNodes[o].nodeName.toLowerCase()&&(i=r.childNodes[o],a=i.nodeName.toLowerCase(),this.metadata[a]=i.textContent.trim())}},get:function(t){return this.metadata[t]||null},has:function(t){return void 0!==this.metadata[t]}},e.Metadata=i},function(t,e,n){"use strict";var r=n(0),i=n(1),a=r.shadow,s=i.getDefaultSetting,o=function(){function t(t,e,n){var r=t.createShader(n);if(t.shaderSource(r,e),t.compileShader(r),!t.getShaderParameter(r,t.COMPILE_STATUS)){var i=t.getShaderInfoLog(r);throw new Error("Error during shader compilation: "+i)}return r}function e(e,n){return t(e,n,e.VERTEX_SHADER)}function n(e,n){return t(e,n,e.FRAGMENT_SHADER)}function r(t,e){for(var n=t.createProgram(),r=0,i=e.length;r<i;++r)t.attachShader(n,e[r]);if(t.linkProgram(n),!t.getProgramParameter(n,t.LINK_STATUS)){var a=t.getProgramInfoLog(n);throw new Error("Error during program linking: "+a)}return n}function i(t,e,n){t.activeTexture(n);var r=t.createTexture();return t.bindTexture(t.TEXTURE_2D,r),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),r}function o(){f||(p=document.createElement("canvas"),f=p.getContext("webgl",{premultipliedalpha:!1}))}function c(){var t,i;o(),t=p,p=null,i=f,f=null;var a=e(i,g),s=n(i,m),c=r(i,[a,s]);i.useProgram(c);var l={};l.gl=i,l.canvas=t,l.resolutionLocation=i.getUniformLocation(c,"u_resolution"),l.positionLocation=i.getAttribLocation(c,"a_position"),l.backdropLocation=i.getUniformLocation(c,"u_backdrop"),l.subtypeLocation=i.getUniformLocation(c,"u_subtype");var h=i.getAttribLocation(c,"a_texCoord"),u=i.getUniformLocation(c,"u_image"),d=i.getUniformLocation(c,"u_mask"),v=i.createBuffer();i.bindBuffer(i.ARRAY_BUFFER,v),i.bufferData(i.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),i.STATIC_DRAW),i.enableVertexAttribArray(h),i.vertexAttribPointer(h,2,i.FLOAT,!1,0,0),i.uniform1i(u,0),i.uniform1i(d,1),A=l}function l(t,e,n){var r=t.width,a=t.height;A||c();var s=A,o=s.canvas,l=s.gl;o.width=r,o.height=a,l.viewport(0,0,l.drawingBufferWidth,l.drawingBufferHeight),l.uniform2f(s.resolutionLocation,r,a),n.backdrop?l.uniform4f(s.resolutionLocation,n.backdrop[0],n.backdrop[1],n.backdrop[2],1):l.uniform4f(s.resolutionLocation,0,0,0,0),l.uniform1i(s.subtypeLocation,"Luminosity"===n.subtype?1:0);var h=i(l,t,l.TEXTURE0),u=i(l,e,l.TEXTURE1),d=l.createBuffer();return l.bindBuffer(l.ARRAY_BUFFER,d),l.bufferData(l.ARRAY_BUFFER,new Float32Array([0,0,r,0,0,a,0,a,r,0,r,a]),l.STATIC_DRAW),l.enableVertexAttribArray(s.positionLocation),l.vertexAttribPointer(s.positionLocation,2,l.FLOAT,!1,0,0),l.clearColor(0,0,0,0),l.enable(l.BLEND),l.blendFunc(l.ONE,l.ONE_MINUS_SRC_ALPHA),l.clear(l.COLOR_BUFFER_BIT),l.drawArrays(l.TRIANGLES,0,6),l.flush(),l.deleteTexture(h),l.deleteTexture(u),l.deleteBuffer(d),o}function h(){var t,i;o(),t=p,p=null,i=f,f=null;var a=e(i,v),s=n(i,b),c=r(i,[a,s]);i.useProgram(c);var l={};l.gl=i,l.canvas=t,l.resolutionLocation=i.getUniformLocation(c,"u_resolution"),l.scaleLocation=i.getUniformLocation(c,"u_scale"),l.offsetLocation=i.getUniformLocation(c,"u_offset"),l.positionLocation=i.getAttribLocation(c,"a_position"),l.colorLocation=i.getAttribLocation(c,"a_color"),y=l}function u(t,e,n,r,i){y||h();var a=y,s=a.canvas,o=a.gl;s.width=t,s.height=e,o.viewport(0,0,o.drawingBufferWidth,o.drawingBufferHeight),o.uniform2f(a.resolutionLocation,t,e);var c,l,u,d=0;for(c=0,l=r.length;c<l;c++)switch(r[c].type){case"lattice":u=r[c].coords.length/r[c].verticesPerRow|0,d+=(u-1)*(r[c].verticesPerRow-1)*6;break;case"triangles":d+=r[c].coords.length}var f=new Float32Array(2*d),p=new Uint8Array(3*d),g=i.coords,m=i.colors,A=0,v=0;for(c=0,l=r.length;c<l;c++){var b=r[c],x=b.coords,S=b.colors;switch(b.type){case"lattice":var w=b.verticesPerRow;u=x.length/w|0;for(var k=1;k<u;k++)for(var C=k*w+1,_=1;_<w;_++,C++)f[A]=g[x[C-w-1]],f[A+1]=g[x[C-w-1]+1],f[A+2]=g[x[C-w]],f[A+3]=g[x[C-w]+1],f[A+4]=g[x[C-1]],f[A+5]=g[x[C-1]+1],p[v]=m[S[C-w-1]],p[v+1]=m[S[C-w-1]+1],p[v+2]=m[S[C-w-1]+2],p[v+3]=m[S[C-w]],p[v+4]=m[S[C-w]+1],p[v+5]=m[S[C-w]+2],p[v+6]=m[S[C-1]],p[v+7]=m[S[C-1]+1],p[v+8]=m[S[C-1]+2],f[A+6]=f[A+2],f[A+7]=f[A+3],f[A+8]=f[A+4],f[A+9]=f[A+5],f[A+10]=g[x[C]],f[A+11]=g[x[C]+1],p[v+9]=p[v+3],p[v+10]=p[v+4],p[v+11]=p[v+5],p[v+12]=p[v+6],p[v+13]=p[v+7],p[v+14]=p[v+8],p[v+15]=m[S[C]],p[v+16]=m[S[C]+1],p[v+17]=m[S[C]+2],A+=12,v+=18;break;case"triangles":for(var T=0,P=x.length;T<P;T++)f[A]=g[x[T]],f[A+1]=g[x[T]+1],p[v]=m[S[T]],p[v+1]=m[S[T]+1],p[v+2]=m[S[T]+2],A+=2,v+=3}}n?o.clearColor(n[0]/255,n[1]/255,n[2]/255,1):o.clearColor(0,0,0,0),o.clear(o.COLOR_BUFFER_BIT);var L=o.createBuffer();o.bindBuffer(o.ARRAY_BUFFER,L),o.bufferData(o.ARRAY_BUFFER,f,o.STATIC_DRAW),o.enableVertexAttribArray(a.positionLocation),o.vertexAttribPointer(a.positionLocation,2,o.FLOAT,!1,0,0);var E=o.createBuffer();return o.bindBuffer(o.ARRAY_BUFFER,E),o.bufferData(o.ARRAY_BUFFER,p,o.STATIC_DRAW),o.enableVertexAttribArray(a.colorLocation),o.vertexAttribPointer(a.colorLocation,3,o.UNSIGNED_BYTE,!1,0,0),o.uniform2f(a.scaleLocation,i.scaleX,i.scaleY),o.uniform2f(a.offsetLocation,i.offsetX,i.offsetY),o.drawArrays(o.TRIANGLES,0,d),o.flush(),o.deleteBuffer(L),o.deleteBuffer(E),s}function d(){A&&A.canvas&&(A.canvas.width=0,A.canvas.height=0),y&&y.canvas&&(y.canvas.width=0,y.canvas.height=0),A=null,y=null}var f,p,g=" attribute vec2 a_position; attribute vec2 a_texCoord; uniform vec2 u_resolution; varying vec2 v_texCoord; void main() { vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); v_texCoord = a_texCoord; } ",m=" precision mediump float; uniform vec4 u_backdrop; uniform int u_subtype; uniform sampler2D u_image; uniform sampler2D u_mask; varying vec2 v_texCoord; void main() { vec4 imageColor = texture2D(u_image, v_texCoord); vec4 maskColor = texture2D(u_mask, v_texCoord); if (u_backdrop.a > 0.0) { maskColor.rgb = maskColor.rgb * maskColor.a + u_backdrop.rgb * (1.0 - maskColor.a); } float lum; if (u_subtype == 0) { lum = maskColor.a; } else { lum = maskColor.r * 0.3 + maskColor.g * 0.59 + maskColor.b * 0.11; } imageColor.a *= lum; imageColor.rgb *= imageColor.a; gl_FragColor = imageColor; } ",A=null,v=" attribute vec2 a_position; attribute vec3 a_color; uniform vec2 u_resolution; uniform vec2 u_scale; uniform vec2 u_offset; varying vec4 v_color; void main() { vec2 position = (a_position + u_offset) * u_scale; vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); v_color = vec4(a_color / 255.0, 1.0); } ",b=" precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; } ",y=null;return{get isEnabled(){if(s("disableWebGL"))return!1;var t=!1;try{o(),t=!!f}catch(t){}return a(this,"isEnabled",t)},composeSMask:l,drawFigures:u,clear:d}}();e.WebGLUtils=o},function(t,e,n){"use strict";var r=n(0),i=n(1),a=n(3),s=n(2),o=n(5),c=n(7),l=n(4),h=r.globalScope,u=r.deprecated,d=r.warn,f=i.LinkTarget,p=i.DEFAULT_LINK_REL,g="undefined"==typeof window;h.PDFJS||(h.PDFJS={});var m=h.PDFJS;m.version="1.8.172",m.build="8ff1fbe7",m.pdfBug=!1, +void 0!==m.verbosity&&r.setVerbosityLevel(m.verbosity),delete m.verbosity,Object.defineProperty(m,"verbosity",{get:function(){return r.getVerbosityLevel()},set:function(t){r.setVerbosityLevel(t)},enumerable:!0,configurable:!0}),m.VERBOSITY_LEVELS=r.VERBOSITY_LEVELS,m.OPS=r.OPS,m.UNSUPPORTED_FEATURES=r.UNSUPPORTED_FEATURES,m.isValidUrl=i.isValidUrl,m.shadow=r.shadow,m.createBlob=r.createBlob,m.createObjectURL=function(t,e){return r.createObjectURL(t,e,m.disableCreateObjectURL)},Object.defineProperty(m,"isLittleEndian",{configurable:!0,get:function(){var t=r.isLittleEndian();return r.shadow(m,"isLittleEndian",t)}}),m.removeNullCharacters=r.removeNullCharacters,m.PasswordResponses=r.PasswordResponses,m.PasswordException=r.PasswordException,m.UnknownErrorException=r.UnknownErrorException,m.InvalidPDFException=r.InvalidPDFException,m.MissingPDFException=r.MissingPDFException,m.UnexpectedResponseException=r.UnexpectedResponseException,m.Util=r.Util,m.PageViewport=r.PageViewport,m.createPromiseCapability=r.createPromiseCapability,m.maxImageSize=void 0===m.maxImageSize?-1:m.maxImageSize,m.cMapUrl=void 0===m.cMapUrl?null:m.cMapUrl,m.cMapPacked=void 0!==m.cMapPacked&&m.cMapPacked,m.disableFontFace=void 0!==m.disableFontFace&&m.disableFontFace,m.imageResourcesPath=void 0===m.imageResourcesPath?"":m.imageResourcesPath,m.disableWorker=void 0!==m.disableWorker&&m.disableWorker,m.workerSrc=void 0===m.workerSrc?null:m.workerSrc,m.workerPort=void 0===m.workerPort?null:m.workerPort,m.disableRange=void 0!==m.disableRange&&m.disableRange,m.disableStream=void 0!==m.disableStream&&m.disableStream,m.disableAutoFetch=void 0!==m.disableAutoFetch&&m.disableAutoFetch,m.pdfBug=void 0!==m.pdfBug&&m.pdfBug,m.postMessageTransfers=void 0===m.postMessageTransfers||m.postMessageTransfers,m.disableCreateObjectURL=void 0!==m.disableCreateObjectURL&&m.disableCreateObjectURL,m.disableWebGL=void 0===m.disableWebGL||m.disableWebGL,m.externalLinkTarget=void 0===m.externalLinkTarget?f.NONE:m.externalLinkTarget,m.externalLinkRel=void 0===m.externalLinkRel?p:m.externalLinkRel,m.isEvalSupported=void 0===m.isEvalSupported||m.isEvalSupported,m.pdfjsNext=void 0!==m.pdfjsNext&&m.pdfjsNext;var A=m.openExternalLinksInNewWindow;delete m.openExternalLinksInNewWindow,Object.defineProperty(m,"openExternalLinksInNewWindow",{get:function(){return m.externalLinkTarget===f.BLANK},set:function(t){if(t&&u('PDFJS.openExternalLinksInNewWindow, please use "PDFJS.externalLinkTarget = PDFJS.LinkTarget.BLANK" instead.'),m.externalLinkTarget!==f.NONE)return void d("PDFJS.externalLinkTarget is already initialized");m.externalLinkTarget=t?f.BLANK:f.NONE},enumerable:!0,configurable:!0}),A&&(m.openExternalLinksInNewWindow=A),m.getDocument=a.getDocument,m.PDFDataRangeTransport=a.PDFDataRangeTransport,m.PDFWorker=a.PDFWorker,Object.defineProperty(m,"hasCanvasTypedArrays",{configurable:!0,get:function(){var t=i.hasCanvasTypedArrays();return r.shadow(m,"hasCanvasTypedArrays",t)}}),m.CustomStyle=i.CustomStyle,m.LinkTarget=f,m.addLinkAttributes=i.addLinkAttributes,m.getFilenameFromUrl=i.getFilenameFromUrl,m.isExternalLinkTargetSet=i.isExternalLinkTargetSet,m.AnnotationLayer=s.AnnotationLayer,m.renderTextLayer=o.renderTextLayer,m.Metadata=c.Metadata,m.SVGGraphics=l.SVGGraphics,m.UnsupportedManager=a._UnsupportedManager,e.globalScope=h,e.isWorker=g,e.PDFJS=h.PDFJS},function(t,e,n){"use strict";function r(t){t.mozCurrentTransform||(t._originalSave=t.save,t._originalRestore=t.restore,t._originalRotate=t.rotate,t._originalScale=t.scale,t._originalTranslate=t.translate,t._originalTransform=t.transform,t._originalSetTransform=t.setTransform,t._transformMatrix=t._transformMatrix||[1,0,0,1,0,0],t._transformStack=[],Object.defineProperty(t,"mozCurrentTransform",{get:function(){return this._transformMatrix}}),Object.defineProperty(t,"mozCurrentTransformInverse",{get:function(){var t=this._transformMatrix,e=t[0],n=t[1],r=t[2],i=t[3],a=t[4],s=t[5],o=e*i-n*r,c=n*r-e*i;return[i/o,n/c,r/c,e/o,(i*a-r*s)/c,(n*a-e*s)/o]}}),t.save=function(){var t=this._transformMatrix;this._transformStack.push(t),this._transformMatrix=t.slice(0,6),this._originalSave()},t.restore=function(){var t=this._transformStack.pop();t&&(this._transformMatrix=t,this._originalRestore())},t.translate=function(t,e){var n=this._transformMatrix;n[4]=n[0]*t+n[2]*e+n[4],n[5]=n[1]*t+n[3]*e+n[5],this._originalTranslate(t,e)},t.scale=function(t,e){var n=this._transformMatrix;n[0]=n[0]*t,n[1]=n[1]*t,n[2]=n[2]*e,n[3]=n[3]*e,this._originalScale(t,e)},t.transform=function(e,n,r,i,a,s){var o=this._transformMatrix;this._transformMatrix=[o[0]*e+o[2]*n,o[1]*e+o[3]*n,o[0]*r+o[2]*i,o[1]*r+o[3]*i,o[0]*a+o[2]*s+o[4],o[1]*a+o[3]*s+o[5]],t._originalTransform(e,n,r,i,a,s)},t.setTransform=function(e,n,r,i,a,s){this._transformMatrix=[e,n,r,i,a,s],t._originalSetTransform(e,n,r,i,a,s)},t.rotate=function(t){var e=Math.cos(t),n=Math.sin(t),r=this._transformMatrix;this._transformMatrix=[r[0]*e+r[2]*n,r[1]*e+r[3]*n,r[0]*-n+r[2]*e,r[1]*-n+r[3]*e,r[4],r[5]],this._originalRotate(t)})}function i(t){var e,n,r,i,a=t.width,s=t.height,o=a+1,c=new Uint8Array(o*(s+1)),l=new Uint8Array([0,2,4,0,1,0,5,4,8,10,0,8,0,2,1,0]),h=a+7&-8,u=t.data,d=new Uint8Array(h*s),f=0;for(e=0,i=u.length;e<i;e++)for(var p=128,g=u[e];p>0;)d[f++]=g&p?0:255,p>>=1;var m=0;for(f=0,0!==d[f]&&(c[0]=1,++m),n=1;n<a;n++)d[f]!==d[f+1]&&(c[n]=d[f]?2:1,++m),f++;for(0!==d[f]&&(c[n]=2,++m),e=1;e<s;e++){f=e*h,r=e*o,d[f-h]!==d[f]&&(c[r]=d[f]?1:8,++m);var A=(d[f]?4:0)+(d[f-h]?8:0);for(n=1;n<a;n++)A=(A>>2)+(d[f+1]?4:0)+(d[f-h+1]?8:0),l[A]&&(c[r+n]=l[A],++m),f++;if(d[f-h]!==d[f]&&(c[r+n]=d[f]?2:4,++m),m>1e3)return null}for(f=h*(s-1),r=e*o,0!==d[f]&&(c[r]=8,++m),n=1;n<a;n++)d[f]!==d[f+1]&&(c[r+n]=d[f]?4:8,++m),f++;if(0!==d[f]&&(c[r+n]=4,++m),m>1e3)return null;var v=new Int32Array([0,o,-1,0,-o,0,0,0,1]),b=[];for(e=0;m&&e<=s;e++){for(var y=e*o,x=y+a;y<x&&!c[y];)y++;if(y!==x){var S,w=[y%o,e],k=c[y],C=y;do{var _=v[k];do{y+=_}while(!c[y]);S=c[y],5!==S&&10!==S?(k=S,c[y]=0):(k=S&51*k>>4,c[y]&=k>>2|k<<2),w.push(y%o),w.push(y/o|0),--m}while(C!==y);b.push(w),--e}}return function(t){t.save(),t.scale(1/a,-1/s),t.translate(0,-s),t.beginPath();for(var e=0,n=b.length;e<n;e++){var r=b[e];t.moveTo(r[0],r[1]);for(var i=2,o=r.length;i<o;i+=2)t.lineTo(r[i],r[i+1])}t.fill(),t.beginPath(),t.restore()}}var a=n(0),s=n(1),o=n(12),c=n(8),l=a.FONT_IDENTITY_MATRIX,h=a.IDENTITY_MATRIX,u=a.ImageKind,d=a.OPS,f=a.TextRenderingMode,p=a.Uint32ArrayView,g=a.Util,m=a.assert,A=a.info,v=a.isNum,b=a.isArray,y=a.isLittleEndian,x=a.error,S=a.shadow,w=a.warn,k=o.TilingPattern,C=o.getShadingPatternFromIR,_=c.WebGLUtils,T=s.hasCanvasTypedArrays,P=16,L={get value(){return S(L,"value",T())}},E={get value(){return S(E,"value",y())}},R=function(){function t(t){this.canvasFactory=t,this.cache=Object.create(null)}return t.prototype={getCanvas:function(t,e,n,i){var a;return void 0!==this.cache[t]?(a=this.cache[t],this.canvasFactory.reset(a,e,n),a.context.setTransform(1,0,0,1,0,0)):(a=this.canvasFactory.create(e,n),this.cache[t]=a),i&&r(a.context),a},clear:function(){for(var t in this.cache){var e=this.cache[t];this.canvasFactory.destroy(e),delete this.cache[t]}}},t}(),I=function(){function t(t){this.alphaIsShape=!1,this.fontSize=0,this.fontSizeScale=1,this.textMatrix=h,this.textMatrixScale=1,this.fontMatrix=l,this.leading=0,this.x=0,this.y=0,this.lineX=0,this.lineY=0,this.charSpacing=0,this.wordSpacing=0,this.textHScale=1,this.textRenderingMode=f.FILL,this.textRise=0,this.fillColor="#000000",this.strokeColor="#000000",this.patternFill=!1,this.fillAlpha=1,this.strokeAlpha=1,this.lineWidth=1,this.activeSMask=null,this.resumeSMaskCtx=null,this.old=t}return t.prototype={clone:function(){return Object.create(this)},setCurrentPoint:function(t,e){this.x=t,this.y=e}},t}(),F=function(){function t(t,e,n,i,a){this.ctx=t,this.current=new I,this.stateStack=[],this.pendingClip=null,this.pendingEOFill=!1,this.res=null,this.xobjs=null,this.commonObjs=e,this.objs=n,this.canvasFactory=i,this.imageLayer=a,this.groupStack=[],this.processingType3=null,this.baseTransform=null,this.baseTransformStack=[],this.groupLevel=0,this.smaskStack=[],this.smaskCounter=0,this.tempSMask=null,this.cachedCanvases=new R(this.canvasFactory),t&&r(t),this.cachedGetSinglePixelWidth=null}function e(t,e){if("undefined"!=typeof ImageData&&e instanceof ImageData)return void t.putImageData(e,0,0);var n,r,i,a,s,o=e.height,c=e.width,l=o%P,h=(o-l)/P,d=0===l?h:h+1,f=t.createImageData(c,P),g=0,m=e.data,A=f.data;if(e.kind===u.GRAYSCALE_1BPP){var v=m.byteLength,b=L.value?new Uint32Array(A.buffer):new p(A),y=b.length,S=c+7>>3,w=4294967295,k=E.value||!L.value?4278190080:255;for(r=0;r<d;r++){for(a=r<h?P:l,n=0,i=0;i<a;i++){for(var C=v-g,_=0,T=C>S?c:8*C-7,R=-8&T,I=0,F=0;_<R;_+=8)F=m[g++],b[n++]=128&F?w:k,b[n++]=64&F?w:k,b[n++]=32&F?w:k,b[n++]=16&F?w:k,b[n++]=8&F?w:k,b[n++]=4&F?w:k,b[n++]=2&F?w:k,b[n++]=1&F?w:k;for(;_<T;_++)0===I&&(F=m[g++],I=128),b[n++]=F&I?w:k,I>>=1}for(;n<y;)b[n++]=0;t.putImageData(f,0,r*P)}}else if(e.kind===u.RGBA_32BPP){for(i=0,s=c*P*4,r=0;r<h;r++)A.set(m.subarray(g,g+s)),g+=s,t.putImageData(f,0,i),i+=P;r<d&&(s=c*l*4,A.set(m.subarray(g,g+s)),t.putImageData(f,0,i))}else if(e.kind===u.RGB_24BPP)for(a=P,s=c*a,r=0;r<d;r++){for(r>=h&&(a=l,s=c*a),n=0,i=s;i--;)A[n++]=m[g++],A[n++]=m[g++],A[n++]=m[g++],A[n++]=255;t.putImageData(f,0,r*P)}else x("bad image kind: "+e.kind)}function n(t,e){for(var n=e.height,r=e.width,i=n%P,a=(n-i)/P,s=0===i?a:a+1,o=t.createImageData(r,P),c=0,l=e.data,h=o.data,u=0;u<s;u++){for(var d=u<a?P:i,f=3,p=0;p<d;p++)for(var g=0,m=0;m<r;m++){if(!g){var A=l[c++];g=128}h[f]=A&g?0:255,f+=4,g>>=1}t.putImageData(o,0,u*P)}}function a(t,e){for(var n=["strokeStyle","fillStyle","fillRule","globalAlpha","lineWidth","lineCap","lineJoin","miterLimit","globalCompositeOperation","font"],r=0,i=n.length;r<i;r++){var a=n[r];void 0!==t[a]&&(e[a]=t[a])}void 0!==t.setLineDash&&(e.setLineDash(t.getLineDash()),e.lineDashOffset=t.lineDashOffset)}function s(t,e,n,r){for(var i=t.length,a=3;a<i;a+=4){var s=t[a];if(0===s)t[a-3]=e,t[a-2]=n,t[a-1]=r;else if(s<255){var o=255-s;t[a-3]=t[a-3]*s+e*o>>8,t[a-2]=t[a-2]*s+n*o>>8,t[a-1]=t[a-1]*s+r*o>>8}}}function o(t,e,n){for(var r=t.length,i=3;i<r;i+=4){var a=n?n[t[i]]:t[i];e[i]=e[i]*a*(1/255)|0}}function c(t,e,n){for(var r=t.length,i=3;i<r;i+=4){var a=77*t[i-3]+152*t[i-2]+28*t[i-1];e[i]=n?e[i]*n[a>>8]>>8:e[i]*a>>16}}function y(t,e,n,r,i,a,l){var h,u=!!a,d=u?a[0]:0,f=u?a[1]:0,p=u?a[2]:0;h="Luminosity"===i?c:o;for(var g=Math.min(r,Math.ceil(1048576/n)),m=0;m<r;m+=g){var A=Math.min(g,r-m),v=t.getImageData(0,m,n,A),b=e.getImageData(0,m,n,A);u&&s(v.data,d,f,p),h(v.data,b.data,l),t.putImageData(b,0,m)}}function T(t,e,n){var r=e.canvas,i=e.context;t.setTransform(e.scaleX,0,0,e.scaleY,e.offsetX,e.offsetY);var a=e.backdrop||null;if(!e.transferMap&&_.isEnabled){var s=_.composeSMask(n.canvas,r,{subtype:e.subtype,backdrop:a});return t.setTransform(1,0,0,1,0,0),void t.drawImage(s,e.offsetX,e.offsetY)}y(i,n,r.width,r.height,e.subtype,a,e.transferMap),t.drawImage(r,0,0)}var F=["butt","round","square"],O=["miter","round","bevel"],M={},D={};t.prototype={beginDrawing:function(t,e,n){var r=this.ctx.canvas.width,i=this.ctx.canvas.height;if(this.ctx.save(),this.ctx.fillStyle="rgb(255, 255, 255)",this.ctx.fillRect(0,0,r,i),this.ctx.restore(),n){var a=this.cachedCanvases.getCanvas("transparent",r,i,!0);this.compositeCtx=this.ctx,this.transparentCanvas=a.canvas,this.ctx=a.context,this.ctx.save(),this.ctx.transform.apply(this.ctx,this.compositeCtx.mozCurrentTransform)}this.ctx.save(),t&&this.ctx.transform.apply(this.ctx,t),this.ctx.transform.apply(this.ctx,e.transform),this.baseTransform=this.ctx.mozCurrentTransform.slice(),this.imageLayer&&this.imageLayer.beginLayout()},executeOperatorList:function(t,e,n,r){var i=t.argsArray,a=t.fnArray,s=e||0,o=i.length;if(o===s)return s;for(var c,l=o-s>10&&"function"==typeof n,h=l?Date.now()+15:0,u=0,f=this.commonObjs,p=this.objs;;){if(void 0!==r&&s===r.nextBreakPoint)return r.breakIt(s,n),s;if((c=a[s])!==d.dependency)this[c].apply(this,i[s]);else for(var g=i[s],m=0,A=g.length;m<A;m++){var v=g[m],b="g"===v[0]&&"_"===v[1],y=b?f:p;if(!y.isResolved(v))return y.get(v,n),s}if(++s===o)return s;if(l&&++u>10){if(Date.now()>h)return n(),s;u=0}}},endDrawing:function(){null!==this.current.activeSMask&&this.endSMaskGroup(),this.ctx.restore(),this.transparentCanvas&&(this.ctx=this.compositeCtx,this.ctx.save(),this.ctx.setTransform(1,0,0,1,0,0),this.ctx.drawImage(this.transparentCanvas,0,0),this.ctx.restore(),this.transparentCanvas=null),this.cachedCanvases.clear(),_.clear(),this.imageLayer&&this.imageLayer.endLayout()},setLineWidth:function(t){this.current.lineWidth=t,this.ctx.lineWidth=t},setLineCap:function(t){this.ctx.lineCap=F[t]},setLineJoin:function(t){this.ctx.lineJoin=O[t]},setMiterLimit:function(t){this.ctx.miterLimit=t},setDash:function(t,e){var n=this.ctx;void 0!==n.setLineDash&&(n.setLineDash(t),n.lineDashOffset=e)},setRenderingIntent:function(t){},setFlatness:function(t){},setGState:function(t){for(var e=0,n=t.length;e<n;e++){var r=t[e],i=r[0],a=r[1];switch(i){case"LW":this.setLineWidth(a);break;case"LC":this.setLineCap(a);break;case"LJ":this.setLineJoin(a);break;case"ML":this.setMiterLimit(a);break;case"D":this.setDash(a[0],a[1]);break;case"RI":this.setRenderingIntent(a);break;case"FL":this.setFlatness(a);break;case"Font":this.setFont(a[0],a[1]);break;case"CA":this.current.strokeAlpha=r[1];break;case"ca":this.current.fillAlpha=r[1],this.ctx.globalAlpha=r[1];break;case"BM":if(a&&a.name&&"Normal"!==a.name){var s=a.name.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()}).substring(1);this.ctx.globalCompositeOperation=s,this.ctx.globalCompositeOperation!==s&&w('globalCompositeOperation "'+s+'" is not supported')}else this.ctx.globalCompositeOperation="source-over";break;case"SMask":this.current.activeSMask&&(this.stateStack.length>0&&this.stateStack[this.stateStack.length-1].activeSMask===this.current.activeSMask?this.suspendSMaskGroup():this.endSMaskGroup()),this.current.activeSMask=a?this.tempSMask:null,this.current.activeSMask&&this.beginSMaskGroup(),this.tempSMask=null}}},beginSMaskGroup:function(){var t=this.current.activeSMask,e=t.canvas.width,n=t.canvas.height,r="smaskGroupAt"+this.groupLevel,i=this.cachedCanvases.getCanvas(r,e,n,!0),s=this.ctx,o=s.mozCurrentTransform;this.ctx.save();var c=i.context;c.scale(1/t.scaleX,1/t.scaleY),c.translate(-t.offsetX,-t.offsetY),c.transform.apply(c,o),t.startTransformInverse=c.mozCurrentTransformInverse,a(s,c),this.ctx=c,this.setGState([["BM","Normal"],["ca",1],["CA",1]]),this.groupStack.push(s),this.groupLevel++},suspendSMaskGroup:function(){var t=this.ctx;this.groupLevel--,this.ctx=this.groupStack.pop(),T(this.ctx,this.current.activeSMask,t),this.ctx.restore(),this.ctx.save(),a(t,this.ctx),this.current.resumeSMaskCtx=t;var e=g.transform(this.current.activeSMask.startTransformInverse,t.mozCurrentTransform);this.ctx.transform.apply(this.ctx,e),t.save(),t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,t.canvas.width,t.canvas.height),t.restore()},resumeSMaskGroup:function(){var t=this.current.resumeSMaskCtx,e=this.ctx;this.ctx=t,this.groupStack.push(e),this.groupLevel++},endSMaskGroup:function(){var t=this.ctx;this.groupLevel--,this.ctx=this.groupStack.pop(),T(this.ctx,this.current.activeSMask,t),this.ctx.restore(),a(t,this.ctx);var e=g.transform(this.current.activeSMask.startTransformInverse,t.mozCurrentTransform);this.ctx.transform.apply(this.ctx,e)},save:function(){this.ctx.save();var t=this.current;this.stateStack.push(t),this.current=t.clone(),this.current.resumeSMaskCtx=null},restore:function(){this.current.resumeSMaskCtx&&this.resumeSMaskGroup(),null===this.current.activeSMask||0!==this.stateStack.length&&this.stateStack[this.stateStack.length-1].activeSMask===this.current.activeSMask||this.endSMaskGroup(),0!==this.stateStack.length&&(this.current=this.stateStack.pop(),this.ctx.restore(),this.pendingClip=null,this.cachedGetSinglePixelWidth=null)},transform:function(t,e,n,r,i,a){this.ctx.transform(t,e,n,r,i,a),this.cachedGetSinglePixelWidth=null},constructPath:function(t,e){for(var n=this.ctx,r=this.current,i=r.x,a=r.y,s=0,o=0,c=t.length;s<c;s++)switch(0|t[s]){case d.rectangle:i=e[o++],a=e[o++];var l=e[o++],h=e[o++];0===l&&(l=this.getSinglePixelWidth()),0===h&&(h=this.getSinglePixelWidth());var u=i+l,f=a+h;this.ctx.moveTo(i,a),this.ctx.lineTo(u,a),this.ctx.lineTo(u,f),this.ctx.lineTo(i,f),this.ctx.lineTo(i,a),this.ctx.closePath();break;case d.moveTo:i=e[o++],a=e[o++],n.moveTo(i,a);break;case d.lineTo:i=e[o++],a=e[o++],n.lineTo(i,a);break;case d.curveTo:i=e[o+4],a=e[o+5],n.bezierCurveTo(e[o],e[o+1],e[o+2],e[o+3],i,a),o+=6;break;case d.curveTo2:n.bezierCurveTo(i,a,e[o],e[o+1],e[o+2],e[o+3]),i=e[o+2],a=e[o+3],o+=4;break;case d.curveTo3:i=e[o+2],a=e[o+3],n.bezierCurveTo(e[o],e[o+1],i,a,i,a),o+=4;break;case d.closePath:n.closePath()}r.setCurrentPoint(i,a)},closePath:function(){this.ctx.closePath()},stroke:function(t){t=void 0===t||t;var e=this.ctx,n=this.current.strokeColor;e.lineWidth=Math.max(.65*this.getSinglePixelWidth(),this.current.lineWidth),e.globalAlpha=this.current.strokeAlpha,n&&n.hasOwnProperty("type")&&"Pattern"===n.type?(e.save(),e.strokeStyle=n.getPattern(e,this),e.stroke(),e.restore()):e.stroke(),t&&this.consumePath(),e.globalAlpha=this.current.fillAlpha},closeStroke:function(){this.closePath(),this.stroke()},fill:function(t){t=void 0===t||t;var e=this.ctx,n=this.current.fillColor,r=this.current.patternFill,i=!1;r&&(e.save(),this.baseTransform&&e.setTransform.apply(e,this.baseTransform),e.fillStyle=n.getPattern(e,this),i=!0),this.pendingEOFill?(e.fill("evenodd"),this.pendingEOFill=!1):e.fill(),i&&e.restore(),t&&this.consumePath()},eoFill:function(){this.pendingEOFill=!0,this.fill()},fillStroke:function(){this.fill(!1),this.stroke(!1),this.consumePath()},eoFillStroke:function(){this.pendingEOFill=!0,this.fillStroke()},closeFillStroke:function(){this.closePath(),this.fillStroke()},closeEOFillStroke:function(){this.pendingEOFill=!0,this.closePath(),this.fillStroke()},endPath:function(){this.consumePath()},clip:function(){this.pendingClip=M},eoClip:function(){this.pendingClip=D},beginText:function(){this.current.textMatrix=h,this.current.textMatrixScale=1,this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0},endText:function(){var t=this.pendingTextPaths,e=this.ctx;if(void 0===t)return void e.beginPath();e.save(),e.beginPath();for(var n=0;n<t.length;n++){var r=t[n];e.setTransform.apply(e,r.transform),e.translate(r.x,r.y),r.addToPath(e,r.fontSize)}e.restore(),e.clip(),e.beginPath(),delete this.pendingTextPaths},setCharSpacing:function(t){this.current.charSpacing=t},setWordSpacing:function(t){this.current.wordSpacing=t},setHScale:function(t){this.current.textHScale=t/100},setLeading:function(t){this.current.leading=-t},setFont:function(t,e){var n=this.commonObjs.get(t),r=this.current;if(n||x("Can't find font for "+t),r.fontMatrix=n.fontMatrix?n.fontMatrix:l,0!==r.fontMatrix[0]&&0!==r.fontMatrix[3]||w("Invalid font matrix for font "+t),e<0?(e=-e,r.fontDirection=-1):r.fontDirection=1,this.current.font=n,this.current.fontSize=e,!n.isType3Font){var i=n.loadedName||"sans-serif",a=n.black?"900":n.bold?"bold":"normal",s=n.italic?"italic":"normal",o='"'+i+'", '+n.fallbackName,c=e<16?16:e>100?100:e;this.current.fontSizeScale=e/c;var h=s+" "+a+" "+c+"px "+o;this.ctx.font=h}},setTextRenderingMode:function(t){this.current.textRenderingMode=t},setTextRise:function(t){this.current.textRise=t},moveText:function(t,e){this.current.x=this.current.lineX+=t,this.current.y=this.current.lineY+=e},setLeadingMoveText:function(t,e){this.setLeading(-e),this.moveText(t,e)},setTextMatrix:function(t,e,n,r,i,a){this.current.textMatrix=[t,e,n,r,i,a],this.current.textMatrixScale=Math.sqrt(t*t+e*e),this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0},nextLine:function(){this.moveText(0,this.current.leading)},paintChar:function(t,e,n){var r,i=this.ctx,a=this.current,s=a.font,o=a.textRenderingMode,c=a.fontSize/a.fontSizeScale,l=o&f.FILL_STROKE_MASK,h=!!(o&f.ADD_TO_PATH_FLAG);if((s.disableFontFace||h)&&(r=s.getPathGenerator(this.commonObjs,t)),s.disableFontFace?(i.save(),i.translate(e,n),i.beginPath(),r(i,c),l!==f.FILL&&l!==f.FILL_STROKE||i.fill(),l!==f.STROKE&&l!==f.FILL_STROKE||i.stroke(),i.restore()):(l!==f.FILL&&l!==f.FILL_STROKE||i.fillText(t,e,n),l!==f.STROKE&&l!==f.FILL_STROKE||i.strokeText(t,e,n)),h){(this.pendingTextPaths||(this.pendingTextPaths=[])).push({transform:i.mozCurrentTransform,x:e,y:n,fontSize:c,addToPath:r})}},get isFontSubpixelAAEnabled(){var t=this.canvasFactory.create(10,10).context;t.scale(1.5,1),t.fillText("I",0,10);for(var e=t.getImageData(0,0,10,10).data,n=!1,r=3;r<e.length;r+=4)if(e[r]>0&&e[r]<255){n=!0;break}return S(this,"isFontSubpixelAAEnabled",n)},showText:function(t){var e=this.current,n=e.font;if(n.isType3Font)return this.showType3Text(t);var r=e.fontSize;if(0!==r){var i=this.ctx,a=e.fontSizeScale,s=e.charSpacing,o=e.wordSpacing,c=e.fontDirection,l=e.textHScale*c,h=t.length,u=n.vertical,d=u?1:-1,p=n.defaultVMetrics,g=r*e.fontMatrix[0],m=e.textRenderingMode===f.FILL&&!n.disableFontFace;i.save(),i.transform.apply(i,e.textMatrix),i.translate(e.x,e.y+e.textRise),e.patternFill&&(i.fillStyle=e.fillColor.getPattern(i,this)),c>0?i.scale(l,-1):i.scale(l,1);var A=e.lineWidth,b=e.textMatrixScale;if(0===b||0===A){var y=e.textRenderingMode&f.FILL_STROKE_MASK;y!==f.STROKE&&y!==f.FILL_STROKE||(this.cachedGetSinglePixelWidth=null,A=.65*this.getSinglePixelWidth())}else A/=b;1!==a&&(i.scale(a,a),A/=a),i.lineWidth=A;var x,S=0;for(x=0;x<h;++x){var w=t[x];if(v(w))S+=d*w*r/1e3;else{var k,C,_,T,P=!1,L=(w.isSpace?o:0)+s,E=w.fontChar,R=w.accent,I=w.width;if(u){var F,O,M;F=w.vmetric||p,O=w.vmetric?F[1]:.5*I,O=-O*g,M=F[2]*g,I=F?-F[0]:I,k=O/a,C=(S+M)/a}else k=S/a,C=0;if(n.remeasure&&I>0){var D=1e3*i.measureText(E).width/r*a;if(I<D&&this.isFontSubpixelAAEnabled){var N=I/D;P=!0,i.save(),i.scale(N,1),k/=N}else I!==D&&(k+=(I-D)/2e3*r/a)}(w.isInFont||n.missingFile)&&(m&&!R?i.fillText(E,k,C):(this.paintChar(E,k,C),R&&(_=k+R.offset.x/a,T=C-R.offset.y/a,this.paintChar(R.fontChar,_,T))));S+=I*g+L*c,P&&i.restore()}}u?e.y-=S*l:e.x+=S*l,i.restore()}},showType3Text:function(t){var e,n,r,i,a=this.ctx,s=this.current,o=s.font,c=s.fontSize,h=s.fontDirection,u=o.vertical?1:-1,d=s.charSpacing,p=s.wordSpacing,m=s.textHScale*h,A=s.fontMatrix||l,b=t.length,y=s.textRenderingMode===f.INVISIBLE;if(!y&&0!==c){for(this.cachedGetSinglePixelWidth=null,a.save(),a.transform.apply(a,s.textMatrix),a.translate(s.x,s.y),a.scale(m,h),e=0;e<b;++e)if(n=t[e],v(n))i=u*n*c/1e3,this.ctx.translate(i,0),s.x+=i*m;else{var x=(n.isSpace?p:0)+d,S=o.charProcOperatorList[n.operatorListId];if(S){this.processingType3=n,this.save(),a.scale(c,c),a.transform.apply(a,A),this.executeOperatorList(S),this.restore();var k=g.applyTransform([n.width,0],A);r=k[0]*c+x,a.translate(r,0),s.x+=r*m}else w('Type3 character "'+n.operatorListId+'" is not available')}a.restore(),this.processingType3=null}},setCharWidth:function(t,e){},setCharWidthAndBounds:function(t,e,n,r,i,a){this.ctx.rect(n,r,i-n,a-r),this.clip(),this.endPath()},getColorN_Pattern:function(e){var n;if("TilingPattern"===e[0]){var r=e[1],i=this.baseTransform||this.ctx.mozCurrentTransform.slice(),a=this,s={createCanvasGraphics:function(e){return new t(e,a.commonObjs,a.objs,a.canvasFactory)}};n=new k(e,r,this.ctx,s,i)}else n=C(e);return n},setStrokeColorN:function(){this.current.strokeColor=this.getColorN_Pattern(arguments)},setFillColorN:function(){this.current.fillColor=this.getColorN_Pattern(arguments),this.current.patternFill=!0},setStrokeRGBColor:function(t,e,n){var r=g.makeCssRgb(t,e,n);this.ctx.strokeStyle=r,this.current.strokeColor=r},setFillRGBColor:function(t,e,n){var r=g.makeCssRgb(t,e,n);this.ctx.fillStyle=r,this.current.fillColor=r,this.current.patternFill=!1},shadingFill:function(t){var e=this.ctx;this.save();var n=C(t);e.fillStyle=n.getPattern(e,this,!0);var r=e.mozCurrentTransformInverse;if(r){var i=e.canvas,a=i.width,s=i.height,o=g.applyTransform([0,0],r),c=g.applyTransform([0,s],r),l=g.applyTransform([a,0],r),h=g.applyTransform([a,s],r),u=Math.min(o[0],c[0],l[0],h[0]),d=Math.min(o[1],c[1],l[1],h[1]),f=Math.max(o[0],c[0],l[0],h[0]),p=Math.max(o[1],c[1],l[1],h[1]);this.ctx.fillRect(u,d,f-u,p-d)}else this.ctx.fillRect(-1e10,-1e10,2e10,2e10);this.restore()},beginInlineImage:function(){x("Should not call beginInlineImage")},beginImageData:function(){x("Should not call beginImageData")},paintFormXObjectBegin:function(t,e){if(this.save(),this.baseTransformStack.push(this.baseTransform),b(t)&&6===t.length&&this.transform.apply(this,t),this.baseTransform=this.ctx.mozCurrentTransform,b(e)&&4===e.length){var n=e[2]-e[0],r=e[3]-e[1];this.ctx.rect(e[0],e[1],n,r),this.clip(),this.endPath()}},paintFormXObjectEnd:function(){this.restore(),this.baseTransform=this.baseTransformStack.pop()},beginGroup:function(t){this.save();var e=this.ctx;t.isolated||A("TODO: Support non-isolated groups."),t.knockout&&w("Knockout groups not supported.");var n=e.mozCurrentTransform;t.matrix&&e.transform.apply(e,t.matrix),m(t.bbox,"Bounding box is required.");var r=g.getAxialAlignedBoundingBox(t.bbox,e.mozCurrentTransform),i=[0,0,e.canvas.width,e.canvas.height];r=g.intersect(r,i)||[0,0,0,0];var s=Math.floor(r[0]),o=Math.floor(r[1]),c=Math.max(Math.ceil(r[2])-s,1),l=Math.max(Math.ceil(r[3])-o,1),h=1,u=1;c>4096&&(h=c/4096,c=4096),l>4096&&(u=l/4096,l=4096);var d="groupAt"+this.groupLevel;t.smask&&(d+="_smask_"+this.smaskCounter++%2);var f=this.cachedCanvases.getCanvas(d,c,l,!0),p=f.context;p.scale(1/h,1/u),p.translate(-s,-o),p.transform.apply(p,n),t.smask?this.smaskStack.push({canvas:f.canvas,context:p,offsetX:s,offsetY:o,scaleX:h,scaleY:u,subtype:t.smask.subtype,backdrop:t.smask.backdrop,transferMap:t.smask.transferMap||null,startTransformInverse:null}):(e.setTransform(1,0,0,1,0,0),e.translate(s,o),e.scale(h,u)),a(e,p),this.ctx=p,this.setGState([["BM","Normal"],["ca",1],["CA",1]]),this.groupStack.push(e),this.groupLevel++,this.current.activeSMask=null},endGroup:function(t){this.groupLevel--;var e=this.ctx;this.ctx=this.groupStack.pop(),void 0!==this.ctx.imageSmoothingEnabled?this.ctx.imageSmoothingEnabled=!1:this.ctx.mozImageSmoothingEnabled=!1,t.smask?this.tempSMask=this.smaskStack.pop():this.ctx.drawImage(e.canvas,0,0),this.restore()},beginAnnotations:function(){this.save(),this.current=new I,this.baseTransform&&this.ctx.setTransform.apply(this.ctx,this.baseTransform)},endAnnotations:function(){this.restore()},beginAnnotation:function(t,e,n){if(this.save(),b(t)&&4===t.length){var r=t[2]-t[0],i=t[3]-t[1];this.ctx.rect(t[0],t[1],r,i),this.clip(),this.endPath()}this.transform.apply(this,e),this.transform.apply(this,n)},endAnnotation:function(){this.restore()},paintJpegXObject:function(t,e,n){var r=this.objs.get(t);if(!r)return void w("Dependent image isn't ready yet");this.save();var i=this.ctx;if(i.scale(1/e,-1/n),i.drawImage(r,0,0,r.width,r.height,0,-n,e,n),this.imageLayer){var a=i.mozCurrentTransformInverse,s=this.getCanvasPosition(0,0);this.imageLayer.appendImage({objId:t,left:s[0],top:s[1],width:e/a[0],height:n/a[3]})}this.restore()},paintImageMaskXObject:function(t){var e=this.ctx,r=t.width,a=t.height,s=this.current.fillColor,o=this.current.patternFill,c=this.processingType3;if(c&&void 0===c.compiled&&(c.compiled=r<=1e3&&a<=1e3?i({data:t.data,width:r,height:a}):null),c&&c.compiled)return void c.compiled(e);var l=this.cachedCanvases.getCanvas("maskCanvas",r,a),h=l.context;h.save(),n(h,t),h.globalCompositeOperation="source-in",h.fillStyle=o?s.getPattern(h,this):s,h.fillRect(0,0,r,a),h.restore(),this.paintInlineImageXObject(l.canvas)},paintImageMaskXObjectRepeat:function(t,e,r,i){var a=t.width,s=t.height,o=this.current.fillColor,c=this.current.patternFill,l=this.cachedCanvases.getCanvas("maskCanvas",a,s),h=l.context;h.save(),n(h,t),h.globalCompositeOperation="source-in",h.fillStyle=c?o.getPattern(h,this):o,h.fillRect(0,0,a,s),h.restore();for(var u=this.ctx,d=0,f=i.length;d<f;d+=2)u.save(),u.transform(e,0,0,r,i[d],i[d+1]),u.scale(1,-1),u.drawImage(l.canvas,0,0,a,s,0,-1,1,1),u.restore()},paintImageMaskXObjectGroup:function(t){for(var e=this.ctx,r=this.current.fillColor,i=this.current.patternFill,a=0,s=t.length;a<s;a++){var o=t[a],c=o.width,l=o.height,h=this.cachedCanvases.getCanvas("maskCanvas",c,l),u=h.context;u.save(),n(u,o),u.globalCompositeOperation="source-in",u.fillStyle=i?r.getPattern(u,this):r,u.fillRect(0,0,c,l),u.restore(),e.save(),e.transform.apply(e,o.transform),e.scale(1,-1),e.drawImage(h.canvas,0,0,c,l,0,-1,1,1),e.restore()}},paintImageXObject:function(t){var e=this.objs.get(t);if(!e)return void w("Dependent image isn't ready yet");this.paintInlineImageXObject(e)},paintImageXObjectRepeat:function(t,e,n,r){var i=this.objs.get(t);if(!i)return void w("Dependent image isn't ready yet");for(var a=i.width,s=i.height,o=[],c=0,l=r.length;c<l;c+=2)o.push({transform:[e,0,0,n,r[c],r[c+1]],x:0,y:0,w:a,h:s});this.paintInlineImageXObjectGroup(i,o)},paintInlineImageXObject:function(t){var n=t.width,r=t.height,i=this.ctx;this.save(),i.scale(1/n,-1/r);var a,s,o=i.mozCurrentTransformInverse,c=o[0],l=o[1],h=Math.max(Math.sqrt(c*c+l*l),1),u=o[2],d=o[3],f=Math.max(Math.sqrt(u*u+d*d),1);if(t instanceof HTMLElement||!t.data)a=t;else{s=this.cachedCanvases.getCanvas("inlineImage",n,r);var p=s.context;e(p,t),a=s.canvas}for(var g=n,m=r,A="prescale1";h>2&&g>1||f>2&&m>1;){var v=g,b=m;h>2&&g>1&&(v=Math.ceil(g/2),h/=g/v),f>2&&m>1&&(b=Math.ceil(m/2),f/=m/b),s=this.cachedCanvases.getCanvas(A,v,b),p=s.context,p.clearRect(0,0,v,b),p.drawImage(a,0,0,g,m,0,0,v,b),a=s.canvas,g=v,m=b,A="prescale1"===A?"prescale2":"prescale1"}if(i.drawImage(a,0,0,g,m,0,-r,n,r),this.imageLayer){var y=this.getCanvasPosition(0,-r);this.imageLayer.appendImage({imgData:t,left:y[0],top:y[1],width:n/o[0],height:r/o[3]})}this.restore()},paintInlineImageXObjectGroup:function(t,n){var r=this.ctx,i=t.width,a=t.height,s=this.cachedCanvases.getCanvas("inlineImage",i,a);e(s.context,t);for(var o=0,c=n.length;o<c;o++){var l=n[o];if(r.save(),r.transform.apply(r,l.transform),r.scale(1,-1),r.drawImage(s.canvas,l.x,l.y,l.w,l.h,0,-1,1,1),this.imageLayer){var h=this.getCanvasPosition(l.x,l.y);this.imageLayer.appendImage({imgData:t,left:h[0],top:h[1],width:i,height:a})}r.restore()}},paintSolidColorImageMask:function(){this.ctx.fillRect(0,0,1,1)},paintXObject:function(){w("Unsupported 'paintXObject' command.")},markPoint:function(t){},markPointProps:function(t,e){},beginMarkedContent:function(t){},beginMarkedContentProps:function(t,e){},endMarkedContent:function(){},beginCompat:function(){},endCompat:function(){},consumePath:function(){var t=this.ctx;this.pendingClip&&(this.pendingClip===D?t.clip("evenodd"):t.clip(),this.pendingClip=null),t.beginPath()},getSinglePixelWidth:function(t){if(null===this.cachedGetSinglePixelWidth){this.ctx.save();var e=this.ctx.mozCurrentTransformInverse;this.ctx.restore(),this.cachedGetSinglePixelWidth=Math.sqrt(Math.max(e[0]*e[0]+e[1]*e[1],e[2]*e[2]+e[3]*e[3]))}return this.cachedGetSinglePixelWidth},getCanvasPosition:function(t,e){var n=this.ctx.mozCurrentTransform;return[n[0]*t+n[2]*e+n[4],n[1]*t+n[3]*e+n[5]]}};for(var N in d)t.prototype[d[N]]=t.prototype[N];return t}();e.CanvasGraphics=F},function(t,e,n){"use strict";function r(t){this.docId=t,this.styleElement=null,this.nativeFontFaces=[],this.loadTestFontId=0,this.loadingContext={requests:[],nextRequestId:0}}var i=n(0),a=i.assert,s=i.bytesToString,o=i.string32,c=i.shadow,l=i.warn;r.prototype={insertRule:function(t){var e=this.styleElement;e||(e=this.styleElement=document.createElement("style"),e.id="PDFJS_FONT_STYLE_TAG_"+this.docId,document.documentElement.getElementsByTagName("head")[0].appendChild(e));var n=e.sheet;n.insertRule(t,n.cssRules.length)},clear:function(){this.styleElement&&(this.styleElement.remove(),this.styleElement=null),this.nativeFontFaces.forEach(function(t){document.fonts.delete(t)}),this.nativeFontFaces.length=0}};var h=function(){ +return atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA==")};Object.defineProperty(r.prototype,"loadTestFont",{get:function(){return c(this,"loadTestFont",h())},configurable:!0}),r.prototype.addNativeFontFace=function(t){this.nativeFontFaces.push(t),document.fonts.add(t)},r.prototype.bind=function(t,e){for(var n=[],i=[],a=[],s=r.isFontLoadingAPISupported&&!r.isSyncFontLoadingSupported,o=0,c=t.length;o<c;o++){var h=t[o];if(!h.attached&&!1!==h.loading)if(h.attached=!0,s){var u=h.createNativeFontFace();u&&(this.addNativeFontFace(u),a.push(function(t){return t.loaded.catch(function(e){l('Failed to load font "'+t.family+'": '+e)})}(u)))}else{var d=h.createFontFaceRule();d&&(this.insertRule(d),n.push(d),i.push(h))}}var f=this.queueLoadingCallback(e);s?Promise.all(a).then(function(){f.complete()}):n.length>0&&!r.isSyncFontLoadingSupported?this.prepareFontLoadEvent(n,i,f):f.complete()},r.prototype.queueLoadingCallback=function(t){function e(){for(a(!i.end,"completeRequest() cannot be called twice"),i.end=Date.now();n.requests.length>0&&n.requests[0].end;){var t=n.requests.shift();setTimeout(t.callback,0)}}var n=this.loadingContext,r="pdfjs-font-loading-"+n.nextRequestId++,i={id:r,complete:e,callback:t,started:Date.now()};return n.requests.push(i),i},r.prototype.prepareFontLoadEvent=function(t,e,n){function r(t,e){return t.charCodeAt(e)<<24|t.charCodeAt(e+1)<<16|t.charCodeAt(e+2)<<8|255&t.charCodeAt(e+3)}function i(t,e,n,r){return t.substr(0,e)+r+t.substr(e+n)}function a(t,e){return++d>30?(l("Load test font never loaded."),void e()):(u.font="30px "+t,u.fillText(".",0,20),u.getImageData(0,0,1,1).data[3]>0?void e():void setTimeout(a.bind(null,t,e)))}var s,c,h=document.createElement("canvas");h.width=1,h.height=1;var u=h.getContext("2d"),d=0,f="lt"+Date.now()+this.loadTestFontId++,p=this.loadTestFont;p=i(p,976,f.length,f);var g=r(p,16);for(s=0,c=f.length-3;s<c;s+=4)g=g-1482184792+r(f,s)|0;s<f.length&&(g=g-1482184792+r(f+"XXX",s)|0),p=i(p,16,4,o(g));var m="url(data:font/opentype;base64,"+btoa(p)+");",A='@font-face { font-family:"'+f+'";src:'+m+"}";this.insertRule(A);var v=[];for(s=0,c=e.length;s<c;s++)v.push(e[s].loadedName);v.push(f);var b=document.createElement("div");for(b.setAttribute("style","visibility: hidden;width: 10px; height: 10px;position: absolute; top: 0px; left: 0px;"),s=0,c=v.length;s<c;++s){var y=document.createElement("span");y.textContent="Hi",y.style.fontFamily=v[s],b.appendChild(y)}document.body.appendChild(b),a(f,function(){document.body.removeChild(b),n.complete()})},r.isFontLoadingAPISupported="undefined"!=typeof document&&!!document.fonts;var u=function(){if("undefined"==typeof navigator)return!0;var t=!1,e=/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(navigator.userAgent);return e&&e[1]>=14&&(t=!0),t};Object.defineProperty(r,"isSyncFontLoadingSupported",{get:function(){return c(r,"isSyncFontLoadingSupported",u())},enumerable:!0,configurable:!0});var d={get value(){return c(this,"value",i.isEvalSupported())}},f=function(){function t(t,e){this.compiledGlyphs=Object.create(null);for(var n in t)this[n]=t[n];this.options=e}return t.prototype={createNativeFontFace:function(){if(!this.data)return null;if(this.options.disableFontFace)return this.disableFontFace=!0,null;var t=new FontFace(this.loadedName,this.data,{});return this.options.fontRegistry&&this.options.fontRegistry.registerFont(this),t},createFontFaceRule:function(){if(!this.data)return null;if(this.options.disableFontFace)return this.disableFontFace=!0,null;var t=s(new Uint8Array(this.data)),e=this.loadedName,n="url(data:"+this.mimetype+";base64,"+btoa(t)+");",r='@font-face { font-family:"'+e+'";src:'+n+"}";return this.options.fontRegistry&&this.options.fontRegistry.registerFont(this,n),r},getPathGenerator:function(t,e){if(!(e in this.compiledGlyphs)){var n,r,i,a=t.get(this.loadedName+"_path_"+e);if(this.options.isEvalSupported&&d.value){var s,o="";for(r=0,i=a.length;r<i;r++)n=a[r],s=void 0!==n.args?n.args.join(","):"",o+="c."+n.cmd+"("+s+");\n";this.compiledGlyphs[e]=new Function("c","size",o)}else this.compiledGlyphs[e]=function(t,e){for(r=0,i=a.length;r<i;r++)n=a[r],"scale"===n.cmd&&(n.args=[e,-e]),t[n.cmd].apply(t,n.args)}}return this.compiledGlyphs[e]}},t}();e.FontFaceObject=f,e.FontLoader=r},function(t,e,n){"use strict";function r(t){var e=u[t[0]];return e||l("Unknown IR type: "+t[0]),e.fromIR(t)}var i=n(0),a=n(8),s=i.Util,o=i.info,c=i.isArray,l=i.error,h=a.WebGLUtils,u={};u.RadialAxial={fromIR:function(t){var e=t[1],n=t[2],r=t[3],i=t[4],a=t[5],s=t[6];return{type:"Pattern",getPattern:function(t){var o;"axial"===e?o=t.createLinearGradient(r[0],r[1],i[0],i[1]):"radial"===e&&(o=t.createRadialGradient(r[0],r[1],a,i[0],i[1],s));for(var c=0,l=n.length;c<l;++c){var h=n[c];o.addColorStop(h[0],h[1])}return o}}}};var d=function(){function t(t,e,n,r,i,a,s,o){var c,l=e.coords,h=e.colors,u=t.data,d=4*t.width;l[n+1]>l[r+1]&&(c=n,n=r,r=c,c=a,a=s,s=c),l[r+1]>l[i+1]&&(c=r,r=i,i=c,c=s,s=o,o=c),l[n+1]>l[r+1]&&(c=n,n=r,r=c,c=a,a=s,s=c);var f=(l[n]+e.offsetX)*e.scaleX,p=(l[n+1]+e.offsetY)*e.scaleY,g=(l[r]+e.offsetX)*e.scaleX,m=(l[r+1]+e.offsetY)*e.scaleY,A=(l[i]+e.offsetX)*e.scaleX,v=(l[i+1]+e.offsetY)*e.scaleY;if(!(p>=v))for(var b,y,x,S,w,k,C,_,T,P=h[a],L=h[a+1],E=h[a+2],R=h[s],I=h[s+1],F=h[s+2],O=h[o],M=h[o+1],D=h[o+2],N=Math.round(p),j=Math.round(v),U=N;U<=j;U++){U<m?(T=U<p?0:p===m?1:(p-U)/(p-m),b=f-(f-g)*T,y=P-(P-R)*T,x=L-(L-I)*T,S=E-(E-F)*T):(T=U>v?1:m===v?0:(m-U)/(m-v),b=g-(g-A)*T,y=R-(R-O)*T,x=I-(I-M)*T,S=F-(F-D)*T),T=U<p?0:U>v?1:(p-U)/(p-v),w=f-(f-A)*T,k=P-(P-O)*T,C=L-(L-M)*T,_=E-(E-D)*T;for(var B=Math.round(Math.min(b,w)),W=Math.round(Math.max(b,w)),G=d*U+4*B,X=B;X<=W;X++)T=(b-X)/(b-w),T=T<0?0:T>1?1:T,u[G++]=y-(y-k)*T|0,u[G++]=x-(x-C)*T|0,u[G++]=S-(S-_)*T|0,u[G++]=255}}function e(e,n,r){var i,a,s=n.coords,o=n.colors;switch(n.type){case"lattice":var c=n.verticesPerRow,h=Math.floor(s.length/c)-1,u=c-1;for(i=0;i<h;i++)for(var d=i*c,f=0;f<u;f++,d++)t(e,r,s[d],s[d+1],s[d+c],o[d],o[d+1],o[d+c]),t(e,r,s[d+c+1],s[d+1],s[d+c],o[d+c+1],o[d+1],o[d+c]);break;case"triangles":for(i=0,a=s.length;i<a;i+=3)t(e,r,s[i],s[i+1],s[i+2],o[i],o[i+1],o[i+2]);break;default:l("illigal figure")}}function n(t,n,r,i,a,s,o){var c,l,u,d,f=Math.floor(t[0]),p=Math.floor(t[1]),g=Math.ceil(t[2])-f,m=Math.ceil(t[3])-p,A=Math.min(Math.ceil(Math.abs(g*n[0]*1.1)),3e3),v=Math.min(Math.ceil(Math.abs(m*n[1]*1.1)),3e3),b=g/A,y=m/v,x={coords:r,colors:i,offsetX:-f,offsetY:-p,scaleX:1/b,scaleY:1/y},S=A+4,w=v+4;if(h.isEnabled)c=h.drawFigures(A,v,s,a,x),l=o.getCanvas("mesh",S,w,!1),l.context.drawImage(c,2,2),c=l.canvas;else{l=o.getCanvas("mesh",S,w,!1);var k=l.context,C=k.createImageData(A,v);if(s){var _=C.data;for(u=0,d=_.length;u<d;u+=4)_[u]=s[0],_[u+1]=s[1],_[u+2]=s[2],_[u+3]=255}for(u=0;u<a.length;u++)e(C,a[u],x);k.putImageData(C,2,2),c=l.canvas}return{canvas:c,offsetX:f-2*b,offsetY:p-2*y,scaleX:b,scaleY:y}}return n}();u.Mesh={fromIR:function(t){var e=t[2],n=t[3],r=t[4],i=t[5],a=t[6],o=t[8];return{type:"Pattern",getPattern:function(t,c,l){var h;if(l)h=s.singularValueDecompose2dScale(t.mozCurrentTransform);else if(h=s.singularValueDecompose2dScale(c.baseTransform),a){var u=s.singularValueDecompose2dScale(a);h=[h[0]*u[0],h[1]*u[1]]}var f=d(i,h,e,n,r,l?null:o,c.cachedCanvases);return l||(t.setTransform.apply(t,c.baseTransform),a&&t.transform.apply(t,a)),t.translate(f.offsetX,f.offsetY),t.scale(f.scaleX,f.scaleY),t.createPattern(f.canvas,"no-repeat")}}}},u.Dummy={fromIR:function(){return{type:"Pattern",getPattern:function(){return"hotpink"}}}};var f=function(){function t(t,e,n,r,i){this.operatorList=t[2],this.matrix=t[3]||[1,0,0,1,0,0],this.bbox=s.normalizeRect(t[4]),this.xstep=t[5],this.ystep=t[6],this.paintType=t[7],this.tilingType=t[8],this.color=e,this.canvasGraphicsFactory=r,this.baseTransform=i,this.type="Pattern",this.ctx=n}var e={COLORED:1,UNCOLORED:2};return t.prototype={createPatternCanvas:function(t){var e=this.operatorList,n=this.bbox,r=this.xstep,i=this.ystep,a=this.paintType,c=this.tilingType,l=this.color,h=this.canvasGraphicsFactory;o("TilingType: "+c);var u=n[0],d=n[1],f=n[2],p=n[3],g=[u,d],m=[u+r,d+i],A=m[0]-g[0],v=m[1]-g[1],b=s.singularValueDecompose2dScale(this.matrix),y=s.singularValueDecompose2dScale(this.baseTransform),x=[b[0]*y[0],b[1]*y[1]];A=Math.min(Math.ceil(Math.abs(A*x[0])),3e3),v=Math.min(Math.ceil(Math.abs(v*x[1])),3e3);var S=t.cachedCanvases.getCanvas("pattern",A,v,!0),w=S.context,k=h.createCanvasGraphics(w);k.groupLevel=t.groupLevel,this.setFillAndStrokeStyleToContext(w,a,l),this.setScale(A,v,r,i),this.transformToScale(k);var C=[1,0,0,1,-g[0],-g[1]];return k.transform.apply(k,C),this.clipBbox(k,n,u,d,f,p),k.executeOperatorList(e),S.canvas},setScale:function(t,e,n,r){this.scale=[t/n,e/r]},transformToScale:function(t){var e=this.scale,n=[e[0],0,0,e[1],0,0];t.transform.apply(t,n)},scaleToContext:function(){var t=this.scale;this.ctx.scale(1/t[0],1/t[1])},clipBbox:function(t,e,n,r,i,a){if(c(e)&&4===e.length){var s=i-n,o=a-r;t.ctx.rect(n,r,s,o),t.clip(),t.endPath()}},setFillAndStrokeStyleToContext:function(t,n,r){switch(n){case e.COLORED:var i=this.ctx;t.fillStyle=i.fillStyle,t.strokeStyle=i.strokeStyle;break;case e.UNCOLORED:var a=s.makeCssRgb(r[0],r[1],r[2]);t.fillStyle=a,t.strokeStyle=a;break;default:l("Unsupported paint type: "+n)}},getPattern:function(t,e){var n=this.createPatternCanvas(e);return t=this.ctx,t.setTransform.apply(t,this.baseTransform),t.transform.apply(t,this.matrix),this.scaleToContext(),t.createPattern(n,"repeat")}},t}();e.getShadingPatternFromIR=r,e.TilingPattern=f},function(t,e,n){"use strict";var r=n(0),i=n(9),a=n(3),s=n(5),o=n(2),c=n(1),l=n(4);e.PDFJS=i.PDFJS,e.build=a.build,e.version=a.version,e.getDocument=a.getDocument,e.PDFDataRangeTransport=a.PDFDataRangeTransport,e.PDFWorker=a.PDFWorker,e.renderTextLayer=s.renderTextLayer,e.AnnotationLayer=o.AnnotationLayer,e.CustomStyle=c.CustomStyle,e.createPromiseCapability=r.createPromiseCapability,e.PasswordResponses=r.PasswordResponses,e.InvalidPDFException=r.InvalidPDFException,e.MissingPDFException=r.MissingPDFException,e.SVGGraphics=l.SVGGraphics,e.UnexpectedResponseException=r.UnexpectedResponseException,e.OPS=r.OPS,e.UNSUPPORTED_FEATURES=r.UNSUPPORTED_FEATURES,e.isValidUrl=c.isValidUrl,e.createValidAbsoluteUrl=r.createValidAbsoluteUrl,e.createObjectURL=r.createObjectURL,e.removeNullCharacters=r.removeNullCharacters,e.shadow=r.shadow,e.createBlob=r.createBlob,e.RenderingCancelledException=c.RenderingCancelledException,e.getFilenameFromUrl=c.getFilenameFromUrl,e.addLinkAttributes=c.addLinkAttributes},function(t,e,n){"use strict";(function(t){if("undefined"==typeof PDFJS||!PDFJS.compatibilityChecked){var e="undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:void 0,n="undefined"!=typeof navigator&&navigator.userAgent||"",r=/Android/.test(n),i=/Android\s[0-2][^\d]/.test(n),a=/Android\s[0-4][^\d]/.test(n),s=n.indexOf("Chrom")>=0,o=/Chrome\/(39|40)\./.test(n),c=n.indexOf("CriOS")>=0,l=n.indexOf("Trident")>=0,h=/\b(iPad|iPhone|iPod)(?=;)/.test(n),u=n.indexOf("Opera")>=0,d=/Safari\//.test(n)&&!/(Chrome\/|Android\s)/.test(n),f="object"==typeof window&&"object"==typeof document;"undefined"==typeof PDFJS&&(e.PDFJS={}),PDFJS.compatibilityChecked=!0,function(){function t(t,e){return new r(this.slice(t,e))}function n(t,e){arguments.length<2&&(e=0);for(var n=0,r=t.length;n<r;++n,++e)this[e]=255&t[n]}function r(e){var r,i,a;if("number"==typeof e)for(r=[],i=0;i<e;++i)r[i]=0;else if("slice"in e)r=e.slice(0);else for(r=[],i=0,a=e.length;i<a;++i)r[i]=e[i];return r.subarray=t,r.buffer=r,r.byteLength=r.length,r.set=n,"object"==typeof e&&e.buffer&&(r.buffer=e.buffer),r}if("undefined"!=typeof Uint8Array)return void 0===Uint8Array.prototype.subarray&&(Uint8Array.prototype.subarray=function(t,e){return new Uint8Array(this.slice(t,e))},Float32Array.prototype.subarray=function(t,e){return new Float32Array(this.slice(t,e))}),void("undefined"==typeof Float64Array&&(e.Float64Array=Float32Array));e.Uint8Array=r,e.Int8Array=r,e.Uint32Array=r,e.Int32Array=r,e.Uint16Array=r,e.Float32Array=r,e.Float64Array=r}(),function(){e.URL||(e.URL=e.webkitURL)}(),function(){if(void 0!==Object.defineProperty){var t=!0;try{f&&Object.defineProperty(new Image,"id",{value:"test"});var e=function(){};e.prototype={get id(){}},Object.defineProperty(new e,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(e){t=!1}if(t)return}Object.defineProperty=function(t,e,n){delete t[e],"get"in n&&t.__defineGetter__(e,n.get),"set"in n&&t.__defineSetter__(e,n.set),"value"in n&&(t.__defineSetter__(e,function(t){return this.__defineGetter__(e,function(){return t}),t}),t[e]=n.value)}}(),function(){if("undefined"!=typeof XMLHttpRequest){var t=XMLHttpRequest.prototype,e=new XMLHttpRequest;if("overrideMimeType"in e||Object.defineProperty(t,"overrideMimeType",{value:function(t){}}),!("responseType"in e)){if(Object.defineProperty(t,"responseType",{get:function(){return this._responseType||"text"},set:function(t){"text"!==t&&"arraybuffer"!==t||(this._responseType=t,"arraybuffer"===t&&"function"==typeof this.overrideMimeType&&this.overrideMimeType("text/plain; charset=x-user-defined"))}}),"undefined"!=typeof VBArray)return void Object.defineProperty(t,"response",{get:function(){return"arraybuffer"===this.responseType?new Uint8Array(new VBArray(this.responseBody).toArray()):this.responseText}});Object.defineProperty(t,"response",{get:function(){if("arraybuffer"!==this.responseType)return this.responseText;var t,e=this.responseText,n=e.length,r=new Uint8Array(n);for(t=0;t<n;++t)r[t]=255&e.charCodeAt(t);return r.buffer}})}}}(),function(){if(!("btoa"in e)){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";e.btoa=function(e){var n,r,i="";for(n=0,r=e.length;n<r;n+=3){var a=255&e.charCodeAt(n),s=255&e.charCodeAt(n+1),o=255&e.charCodeAt(n+2),c=a>>2,l=(3&a)<<4|s>>4,h=n+1<r?(15&s)<<2|o>>6:64,u=n+2<r?63&o:64;i+=t.charAt(c)+t.charAt(l)+t.charAt(h)+t.charAt(u)}return i}}}(),function(){if(!("atob"in e)){e.atob=function(t){if(t=t.replace(/=+$/,""),t.length%4==1)throw new Error("bad atob input");for(var e,n,r=0,i=0,a="";n=t.charAt(i++);~n&&(e=r%4?64*e+n:n,r++%4)?a+=String.fromCharCode(255&e>>(-2*r&6)):0)n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(n);return a}}}(),function(){void 0===Function.prototype.bind&&(Function.prototype.bind=function(t){var e=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.concat(Array.prototype.slice.call(arguments));return e.apply(t,r)}})}(),function(){if(f){"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var t={},e=0,n=this.attributes.length;e<n;e++){var r=this.attributes[e];if("data-"===r.name.substring(0,5)){t[r.name.substring(5).replace(/\-([a-z])/g,function(t,e){return e.toUpperCase()})]=r.value}}return Object.defineProperty(this,"_dataset",{value:t,writable:!1,enumerable:!1}),t},enumerable:!0})}}(),function(){function t(t,e,n,r){var i=t.className||"",a=i.split(/\s+/g);""===a[0]&&a.shift();var s=a.indexOf(e);return s<0&&n&&a.push(e),s>=0&&r&&a.splice(s,1),t.className=a.join(" "),s>=0}if(f){if(!("classList"in document.createElement("div"))){var e={add:function(e){t(this.element,e,!0,!1)},contains:function(e){return t(this.element,e,!1,!1)},remove:function(e){t(this.element,e,!1,!0)},toggle:function(e){t(this.element,e,!0,!0)}};Object.defineProperty(HTMLElement.prototype,"classList",{get:function(){if(this._classList)return this._classList;var t=Object.create(e,{element:{value:this,writable:!1,enumerable:!0}});return Object.defineProperty(this,"_classList",{value:t,writable:!1,enumerable:!1}),t},enumerable:!0})}}}(),function(){if(!("undefined"==typeof importScripts||"console"in e)){var t={},n={log:function(){var t=Array.prototype.slice.call(arguments);e.postMessage({targetName:"main",action:"console_log",data:t})},error:function(){var t=Array.prototype.slice.call(arguments);e.postMessage({targetName:"main",action:"console_error",data:t})},time:function(e){t[e]=Date.now()},timeEnd:function(e){var n=t[e];if(!n)throw new Error("Unknown timer name "+e);this.log("Timer:",e,Date.now()-n)}};e.console=n}}(),function(){if(f)"console"in window?"bind"in console.log||(console.log=function(t){return function(e){return t(e)}}(console.log),console.error=function(t){return function(e){return t(e)}}(console.error),console.warn=function(t){return function(e){return t(e)}}(console.warn)):window.console={log:function(){},error:function(){},warn:function(){}}}(),function(){function t(t){e(t.target)&&t.stopPropagation()}function e(t){return t.disabled||t.parentNode&&e(t.parentNode)}u&&document.addEventListener("click",t,!0)}(),function(){(l||c)&&(PDFJS.disableCreateObjectURL=!0)}(),function(){"undefined"!=typeof navigator&&("language"in navigator||(PDFJS.locale=navigator.userLanguage||"en-US"))}(),function(){(d||i||o||h)&&(PDFJS.disableRange=!0,PDFJS.disableStream=!0)}(),function(){f&&(history.pushState&&!i||(PDFJS.disableHistory=!0))}(),function(){if(f)if(window.CanvasPixelArray)"function"!=typeof window.CanvasPixelArray.prototype.set&&(window.CanvasPixelArray.prototype.set=function(t){for(var e=0,n=this.length;e<n;e++)this[e]=t[e]});else{var t,e=!1;if(s?(t=n.match(/Chrom(e|ium)\/([0-9]+)\./),e=t&&parseInt(t[2])<21):r?e=a:d&&(t=n.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//),e=t&&parseInt(t[1])<6),e){var i=window.CanvasRenderingContext2D.prototype,o=i.createImageData;i.createImageData=function(t,e){var n=o.call(this,t,e);return n.data.set=function(t){for(var e=0,n=this.length;e<n;e++)this[e]=t[e]},n},i=null}}}(),function(){function t(){window.requestAnimationFrame=function(t){return window.setTimeout(t,20)},window.cancelAnimationFrame=function(t){window.clearTimeout(t)}}if(f)h?t():"requestAnimationFrame"in window||(window.requestAnimationFrame=window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame,"requestAnimationFrame"in window||t())}(),function(){(h||r)&&(PDFJS.maxCanvasPixels=5242880)}(),function(){f&&l&&window.parent!==window&&(PDFJS.disableFullscreen=!0)}(),function(){f&&("currentScript"in document||Object.defineProperty(document,"currentScript",{get:function(){var t=document.getElementsByTagName("script");return t[t.length-1]},enumerable:!0,configurable:!0}))}(),function(){if(f){var t=document.createElement("input");try{t.type="number"}catch(r){var e=t.constructor.prototype,n=Object.getOwnPropertyDescriptor(e,"type");Object.defineProperty(e,"type",{get:function(){return n.get.call(this)},set:function(t){n.set.call(this,"number"===t?"text":t)},enumerable:!0,configurable:!0})}}}(),function(){if(f&&document.attachEvent){var t=document.constructor.prototype,e=Object.getOwnPropertyDescriptor(t,"readyState");Object.defineProperty(t,"readyState",{get:function(){var t=e.get.call(this);return"interactive"===t?"loading":t},set:function(t){e.set.call(this,t)},enumerable:!0,configurable:!0})}}(),function(){f&&void 0===Element.prototype.remove&&(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)})}(),function(){if(e.Promise)return"function"!=typeof e.Promise.all&&(e.Promise.all=function(t){var n,r,i=0,a=[],s=new e.Promise(function(t,e){n=t,r=e});return t.forEach(function(t,e){i++,t.then(function(t){a[e]=t,0===--i&&n(a)},r)}),0===i&&n(a),s}),"function"!=typeof e.Promise.resolve&&(e.Promise.resolve=function(t){return new e.Promise(function(e){e(t)})}),"function"!=typeof e.Promise.reject&&(e.Promise.reject=function(t){return new e.Promise(function(e,n){n(t)})}),void("function"!=typeof e.Promise.prototype.catch&&(e.Promise.prototype.catch=function(t){return e.Promise.prototype.then(void 0,t)}));var t=2,n={handlers:[],running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(t){0!==t._status&&(this.handlers=this.handlers.concat(t._handlers),t._handlers=[],this.running||(this.running=!0,setTimeout(this.runHandlers.bind(this),0)))},runHandlers:function(){for(var e=Date.now()+1;this.handlers.length>0;){var n=this.handlers.shift(),r=n.thisPromise._status,i=n.thisPromise._value;try{1===r?"function"==typeof n.onResolve&&(i=n.onResolve(i)):"function"==typeof n.onReject&&(i=n.onReject(i),r=1,n.thisPromise._unhandledRejection&&this.removeUnhandeledRejection(n.thisPromise))}catch(e){r=t,i=e}if(n.nextPromise._updateStatus(r,i),Date.now()>=e)break}if(this.handlers.length>0)return void setTimeout(this.runHandlers.bind(this),0);this.running=!1},addUnhandledRejection:function(t){this.unhandledRejections.push({promise:t,time:Date.now()}),this.scheduleRejectionCheck()},removeUnhandeledRejection:function(t){t._unhandledRejection=!1;for(var e=0;e<this.unhandledRejections.length;e++)this.unhandledRejections[e].promise===t&&(this.unhandledRejections.splice(e),e--)},scheduleRejectionCheck:function(){this.pendingRejectionCheck||(this.pendingRejectionCheck=!0,setTimeout(function(){this.pendingRejectionCheck=!1;for(var t=Date.now(),e=0;e<this.unhandledRejections.length;e++)if(t-this.unhandledRejections[e].time>500){var n=this.unhandledRejections[e].promise._value,r="Unhandled rejection: "+n;n.stack&&(r+="\n"+n.stack);try{throw new Error(r)}catch(t){console.warn(r)}this.unhandledRejections.splice(e),e--}this.unhandledRejections.length&&this.scheduleRejectionCheck()}.bind(this),500))}},r=function(t){this._status=0,this._handlers=[];try{t.call(this,this._resolve.bind(this),this._reject.bind(this))}catch(t){this._reject(t)}};r.all=function(e){function n(e){s._status!==t&&(c=[],a(e))}var i,a,s=new r(function(t,e){i=t,a=e}),o=e.length,c=[];if(0===o)return i(c),s;for(var l=0,h=e.length;l<h;++l){var u=e[l],d=function(e){return function(n){s._status!==t&&(c[e]=n,0===--o&&i(c))}}(l);r.isPromise(u)?u.then(d,n):d(u)}return s},r.isPromise=function(t){return t&&"function"==typeof t.then},r.resolve=function(t){return new r(function(e){e(t)})},r.reject=function(t){return new r(function(e,n){n(t)})},r.prototype={_status:null,_value:null,_handlers:null,_unhandledRejection:null,_updateStatus:function(e,i){if(1!==this._status&&this._status!==t){if(1===e&&r.isPromise(i))return void i.then(this._updateStatus.bind(this,1),this._updateStatus.bind(this,t));this._status=e,this._value=i,e===t&&0===this._handlers.length&&(this._unhandledRejection=!0,n.addUnhandledRejection(this)),n.scheduleHandlers(this)}},_resolve:function(t){this._updateStatus(1,t)},_reject:function(e){this._updateStatus(t,e)},then:function(t,e){var i=new r(function(t,e){this.resolve=t,this.reject=e});return this._handlers.push({thisPromise:this,onResolve:t,onReject:e,nextPromise:i}),n.scheduleHandlers(this),i},catch:function(t){return this.then(void 0,t)}},e.Promise=r}(),function(){function t(){this.id="$weakmap"+n++}if(!e.WeakMap){var n=0;t.prototype={has:function(t){return!!Object.getOwnPropertyDescriptor(t,this.id)},get:function(t,e){return this.has(t)?t[this.id]:e},set:function(t,e){Object.defineProperty(t,this.id,{value:e,enumerable:!1,configurable:!0})},delete:function(t){delete t[this.id]}},e.WeakMap=t}}(),function(){function t(t){return void 0!==u[t]}function n(){o.call(this),this._isInvalid=!0}function r(t){return""===t&&n.call(this),t.toLowerCase()}function i(t){var e=t.charCodeAt(0);return e>32&&e<127&&-1===[34,35,60,62,63,96].indexOf(e)?t:encodeURIComponent(t)}function a(t){var e=t.charCodeAt(0);return e>32&&e<127&&-1===[34,35,60,62,96].indexOf(e)?t:encodeURIComponent(t)}function s(e,s,o){function c(t){b.push(t)}var l=s||"scheme start",h=0,m="",A=!1,v=!1,b=[];t:for(;(e[h-1]!==f||0===h)&&!this._isInvalid;){var y=e[h];switch(l){case"scheme start":if(!y||!p.test(y)){if(s){c("Invalid scheme.");break t}m="",l="no scheme";continue}m+=y.toLowerCase(),l="scheme";break;case"scheme":if(y&&g.test(y))m+=y.toLowerCase();else{if(":"!==y){if(s){if(y===f)break t;c("Code point not allowed in scheme: "+y);break t}m="",h=0,l="no scheme";continue}if(this._scheme=m,m="",s)break t;t(this._scheme)&&(this._isRelative=!0),l="file"===this._scheme?"relative":this._isRelative&&o&&o._scheme===this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"===y?(this._query="?",l="query"):"#"===y?(this._fragment="#",l="fragment"):y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._schemeData+=i(y));break;case"no scheme":if(o&&t(o._scheme)){l="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!==y||"/"!==e[h+1]){c("Expected /, got: "+y),l="relative";continue}l="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!==this._scheme&&(this._scheme=o._scheme),y===f){this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query=o._query,this._username=o._username,this._password=o._password;break t}if("/"===y||"\\"===y)"\\"===y&&c("\\ is an invalid code point."),l="relative slash";else if("?"===y)this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query="?",this._username=o._username,this._password=o._password,l="query";else{if("#"!==y){var x=e[h+1],S=e[h+2];("file"!==this._scheme||!p.test(y)||":"!==x&&"|"!==x||S!==f&&"/"!==S&&"\\"!==S&&"?"!==S&&"#"!==S)&&(this._host=o._host,this._port=o._port,this._username=o._username,this._password=o._password,this._path=o._path.slice(),this._path.pop()),l="relative path";continue}this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query=o._query,this._fragment="#",this._username=o._username,this._password=o._password,l="fragment"}break;case"relative slash":if("/"!==y&&"\\"!==y){"file"!==this._scheme&&(this._host=o._host,this._port=o._port,this._username=o._username,this._password=o._password),l="relative path";continue}"\\"===y&&c("\\ is an invalid code point."),l="file"===this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!==y){c("Expected '/', got: "+y),l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":if(l="authority ignore slashes","/"!==y){c("Expected '/', got: "+y);continue}break;case"authority ignore slashes":if("/"!==y&&"\\"!==y){l="authority";continue}c("Expected authority, got: "+y);break;case"authority":if("@"===y){A&&(c("@ already seen."),m+="%40"),A=!0;for(var w=0;w<m.length;w++){var k=m[w];if("\t"!==k&&"\n"!==k&&"\r"!==k)if(":"!==k||null!==this._password){var C=i(k);null!==this._password?this._password+=C:this._username+=C}else this._password="";else c("Invalid whitespace in authority.")}m=""}else{if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){h-=m.length,m="",l="host";continue}m+=y}break;case"file host":if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){2!==m.length||!p.test(m[0])||":"!==m[1]&&"|"!==m[1]?0===m.length?l="relative path start":(this._host=r.call(this,m),m="",l="relative path start"):l="relative path";continue}"\t"===y||"\n"===y||"\r"===y?c("Invalid whitespace in file host."):m+=y;break;case"host":case"hostname":if(":"!==y||v){if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){if(this._host=r.call(this,m),m="",l="relative path start",s)break t;continue}"\t"!==y&&"\n"!==y&&"\r"!==y?("["===y?v=!0:"]"===y&&(v=!1),m+=y):c("Invalid code point in host/hostname: "+y)}else if(this._host=r.call(this,m),m="",l="port","hostname"===s)break t;break;case"port":if(/[0-9]/.test(y))m+=y;else{if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y||s){if(""!==m){var _=parseInt(m,10);_!==u[this._scheme]&&(this._port=_+""),m=""}if(s)break t;l="relative path start";continue}"\t"===y||"\n"===y||"\r"===y?c("Invalid code point in port: "+y):n.call(this)}break;case"relative path start":if("\\"===y&&c("'\\' not allowed in path."),l="relative path","/"!==y&&"\\"!==y)continue;break;case"relative path":if(y!==f&&"/"!==y&&"\\"!==y&&(s||"?"!==y&&"#"!==y))"\t"!==y&&"\n"!==y&&"\r"!==y&&(m+=i(y));else{"\\"===y&&c("\\ not allowed in relative path.");var T;(T=d[m.toLowerCase()])&&(m=T),".."===m?(this._path.pop(),"/"!==y&&"\\"!==y&&this._path.push("")):"."===m&&"/"!==y&&"\\"!==y?this._path.push(""):"."!==m&&("file"===this._scheme&&0===this._path.length&&2===m.length&&p.test(m[0])&&"|"===m[1]&&(m=m[0]+":"),this._path.push(m)),m="","?"===y?(this._query="?",l="query"):"#"===y&&(this._fragment="#",l="fragment")}break;case"query":s||"#"!==y?y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._query+=a(y)):(this._fragment="#",l="fragment");break;case"fragment":y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._fragment+=y)}h++}}function o(){this._scheme="",this._schemeData="",this._username="",this._password=null,this._host="",this._port="",this._path=[],this._query="",this._fragment="",this._isInvalid=!1,this._isRelative=!1}function c(t,e){void 0===e||e instanceof c||(e=new c(String(e))),this._url=t,o.call(this);var n=t.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g,"");s.call(this,n,null,e)}var l=!1;try{if("function"==typeof URL&&"object"==typeof URL.prototype&&"origin"in URL.prototype){var h=new URL("b","http://a");h.pathname="c%20d",l="http://a/c%20d"===h.href}}catch(t){}if(!l){var u=Object.create(null);u.ftp=21,u.file=0,u.gopher=70,u.http=80,u.https=443,u.ws=80,u.wss=443;var d=Object.create(null);d["%2e"]=".",d[".%2e"]="..",d["%2e."]="..",d["%2e%2e"]="..";var f,p=/[a-zA-Z]/,g=/[a-zA-Z0-9\+\-\.]/;c.prototype={toString:function(){return this.href},get href(){if(this._isInvalid)return this._url;var t="";return""===this._username&&null===this._password||(t=this._username+(null!==this._password?":"+this._password:"")+"@"),this.protocol+(this._isRelative?"//"+t+this.host:"")+this.pathname+this._query+this._fragment},set href(t){o.call(this),s.call(this,t)},get protocol(){return this._scheme+":"},set protocol(t){this._isInvalid||s.call(this,t+":","scheme start")},get host(){return this._isInvalid?"":this._port?this._host+":"+this._port:this._host},set host(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"host")},get hostname(){return this._host},set hostname(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"hostname")},get port(){return this._port},set port(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"port")},get pathname(){return this._isInvalid?"":this._isRelative?"/"+this._path.join("/"):this._schemeData},set pathname(t){!this._isInvalid&&this._isRelative&&(this._path=[],s.call(this,t,"relative path start"))},get search(){return this._isInvalid||!this._query||"?"===this._query?"":this._query},set search(t){!this._isInvalid&&this._isRelative&&(this._query="?","?"===t[0]&&(t=t.slice(1)),s.call(this,t,"query"))},get hash(){return this._isInvalid||!this._fragment||"#"===this._fragment?"":this._fragment},set hash(t){this._isInvalid||(this._fragment="#","#"===t[0]&&(t=t.slice(1)), +s.call(this,t,"fragment"))},get origin(){var t;if(this._isInvalid||!this._scheme)return"";switch(this._scheme){case"data":case"file":case"javascript":case"mailto":return"null"}return t=this.host,t?this._scheme+"://"+t:""}};var m=e.URL;m&&(c.createObjectURL=function(t){return m.createObjectURL.apply(m,arguments)},c.revokeObjectURL=function(t){m.revokeObjectURL(t)}),e.URL=c}}()}}).call(e,n(6))}])});
\ No newline at end of file diff --git a/vendor/assets/javascripts/pdf.worker.js b/vendor/assets/javascripts/pdf.worker.js index 970caaaba86..6caaf016ebc 100644..100755 --- a/vendor/assets/javascripts/pdf.worker.js +++ b/vendor/assets/javascripts/pdf.worker.js @@ -1,274 +1,4 @@ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define("PDFLab", [], factory); - else if(typeof exports === 'object') - exports["PDFLab"] = factory(); - else - root["PDFLab"] = factory(); -})(this, function() { -return /******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; -/******/ -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 24); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ 0: -/***/ (function(module, exports) { - -// shim for using process in browser -var process = module.exports = {}; - -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. - -var cachedSetTimeout; -var cachedClearTimeout; - -function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); -} -function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); -} -(function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } -} ()) -function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } - - -} -function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } - - - -} -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; - -function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } -} - -function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); -} - -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - - -/***/ }), - -/***/ 1: -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(process) {/* Copyright 2017 Mozilla Foundation +/* Copyright 2017 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -284,7 +14,7 @@ process.umask = function() { return 0; }; */ (function webpackUniversalModuleDefinition(root, factory) { - if(true) + if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("pdfjs-dist/build/pdf.worker", [], factory); @@ -38605,35 +38335,4 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) { /***/ }) /******/ ]); -}); -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0))) - -/***/ }), - -/***/ 24: -/***/ (function(module, exports, __webpack_require__) { - -/* Copyright 2016 Mozilla Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-disable strict */ - -(typeof window !== 'undefined' ? window : {}).pdfjsDistBuildPdfWorker = - __webpack_require__(1); - - -/***/ }) - -/******/ }); });
\ No newline at end of file diff --git a/vendor/assets/javascripts/pdf.worker.min.js b/vendor/assets/javascripts/pdf.worker.min.js new file mode 100755 index 00000000000..3503a8f46ca --- /dev/null +++ b/vendor/assets/javascripts/pdf.worker.min.js @@ -0,0 +1,19 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf.worker",[],t):"object"==typeof exports?exports["pdfjs-dist/build/pdf.worker"]=t():e["pdfjs-dist/build/pdf.worker"]=e.pdfjsDistBuildPdfWorker=t()}(this,function(){return function(e){function t(r){if(a[r])return a[r].exports;var i=a[r]={i:r,l:!1,exports:{}};e[r].call(i.exports,i,i.exports,t);i.l=!0;return i.exports}var a={};t.m=e;t.c=a;t.i=function(e){return e};t.d=function(e,a,r){t.o(e,a)||Object.defineProperty(e,a,{configurable:!1,enumerable:!0,get:r})};t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};t.d(a,"a",a);return a};t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};t.p="";return t(t.s=36)}([function(e,t,a){"use strict";(function(e){function r(e){ae=e}function i(){return ae}function n(e){ae>=$.infos&&console.log("Info: "+e)}function s(e){ae>=$.warnings&&console.log("Warning: "+e)}function o(e){console.log("Deprecated API usage: "+e)}function c(e){if(ae>=$.errors){console.log("Error: "+e);console.log(l())}throw new Error(e)}function l(){try{throw new Error}catch(e){return e.stack?e.stack.split("\n").slice(2).join("\n"):""}}function h(e,t){e||c(t)}function u(e,t){try{var a=new URL(e);if(!a.origin||"null"===a.origin)return!1}catch(e){return!1}var r=new URL(t,a);return a.origin===r.origin}function f(e){if(!e)return!1;switch(e.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}function d(e,t){if(!e)return null;try{var a=t?new URL(e,t):new URL(e);if(f(a))return a}catch(e){}return null}function g(e,t,a){Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!1});return a}function p(e){var t;return function(){if(e){t=Object.create(null);e(t);e=null}return t}}function m(e){if("string"!=typeof e){s("The argument for removeNullCharacters must be a string.");return e}return e.replace(de,"")}function b(e){h(null!==e&&"object"==typeof e&&void 0!==e.length,"Invalid argument for bytesToString");var t=e.length;if(t<8192)return String.fromCharCode.apply(null,e);for(var a=[],r=0;r<t;r+=8192){var i=Math.min(r+8192,t),n=e.subarray(r,i);a.push(String.fromCharCode.apply(null,n))}return a.join("")}function v(e){h("string"==typeof e,"Invalid argument for stringToBytes");for(var t=e.length,a=new Uint8Array(t),r=0;r<t;++r)a[r]=255&e.charCodeAt(r);return a}function y(e){if(void 0!==e.length)return e.length;h(void 0!==e.byteLength);return e.byteLength}function k(e){if(1===e.length&&e[0]instanceof Uint8Array)return e[0];var t,a,r,i=0,n=e.length;for(t=0;t<n;t++){a=e[t];r=y(a);i+=r}var s=0,o=new Uint8Array(i);for(t=0;t<n;t++){a=e[t];a instanceof Uint8Array||(a="string"==typeof a?v(a):new Uint8Array(a));r=a.byteLength;o.set(a,s);s+=r}return o}function w(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e)}function C(e){for(var t=1,a=0;e>t;){t<<=1;a++}return a}function x(e,t){return e[t]<<24>>24}function S(e,t){return e[t]<<8|e[t+1]}function A(e,t){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0}function I(){var e=new Uint8Array(2);e[0]=1;return 1===new Uint16Array(e.buffer)[0]}function B(){try{new Function("");return!0}catch(e){return!1}}function R(e){var t,a=e.length,r=[];if("þ"===e[0]&&"ÿ"===e[1])for(t=2;t<a;t+=2)r.push(String.fromCharCode(e.charCodeAt(t)<<8|e.charCodeAt(t+1)));else for(t=0;t<a;++t){var i=ve[e.charCodeAt(t)];r.push(i?String.fromCharCode(i):e.charAt(t))}return r.join("")}function T(e){return decodeURIComponent(escape(e))}function O(e){return unescape(encodeURIComponent(e))}function P(e){for(var t in e)return!1;return!0}function M(e){return"boolean"==typeof e}function E(e){return"number"==typeof e&&(0|e)===e}function L(e){return"number"==typeof e}function D(e){return"string"==typeof e}function F(e){return e instanceof Array}function q(e){return"object"==typeof e&&null!==e&&void 0!==e.byteLength}function U(e){return 32===e||9===e||13===e||10===e}function N(){return"undefined"==typeof __pdfjsdev_webpack__&&("object"==typeof process&&process+""=="[object process]")}function j(){var e={};e.promise=new Promise(function(t,a){e.resolve=t;e.reject=a});return e}function _(e,t,a){this.sourceName=e;this.targetName=t;this.comObj=a;this.callbackIndex=1;this.postMessageTransfers=!0;var r=this.callbacksCapabilities=Object.create(null),i=this.actionHandler=Object.create(null);this._onComObjOnMessage=function(e){var t=e.data;if(t.targetName===this.sourceName)if(t.isReply){var n=t.callbackId;if(t.callbackId in r){var s=r[n];delete r[n];"error"in t?s.reject(t.error):s.resolve(t.data)}else c("Cannot resolve callback "+n)}else if(t.action in i){var o=i[t.action];if(t.callbackId){var l=this.sourceName,h=t.sourceName;Promise.resolve().then(function(){return o[0].call(o[1],t.data)}).then(function(e){a.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:t.callbackId,data:e})},function(e){e instanceof Error&&(e+="");a.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:t.callbackId,error:e})})}else o[0].call(o[1],t.data)}else c("Unknown action from worker: "+t.action)}.bind(this);a.addEventListener("message",this._onComObjOnMessage)}function z(e,t,a){var r=new Image;r.onload=function(){a.resolve(e,r)};r.onerror=function(){a.resolve(e,null);s("Error during JPEG image loading")};r.src=t}var H=(a(37),"undefined"!=typeof window?window:void 0!==e?e:"undefined"!=typeof self?self:void 0),G=[.001,0,0,.001,0,0],X={FILL:0,STROKE:1,FILL_STROKE:2,INVISIBLE:3,FILL_ADD_TO_PATH:4,STROKE_ADD_TO_PATH:5,FILL_STROKE_ADD_TO_PATH:6,ADD_TO_PATH:7,FILL_STROKE_MASK:3,ADD_TO_PATH_FLAG:4},V={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},W={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},K={INVISIBLE:1,HIDDEN:2,PRINT:4,NOZOOM:8,NOROTATE:16,NOVIEW:32,READONLY:64,LOCKED:128,TOGGLENOVIEW:256,LOCKEDCONTENTS:512},Y={READONLY:1,REQUIRED:2,NOEXPORT:4,MULTILINE:4096,PASSWORD:8192,NOTOGGLETOOFF:16384,RADIO:32768,PUSHBUTTON:65536,COMBO:131072,EDIT:262144,SORT:524288,FILESELECT:1048576,MULTISELECT:2097152,DONOTSPELLCHECK:4194304,DONOTSCROLL:8388608,COMB:16777216,RICHTEXT:33554432,RADIOSINUNISON:33554432,COMMITONSELCHANGE:67108864},J={SOLID:1,DASHED:2,BEVELED:3,INSET:4,UNDERLINE:5},Z={UNKNOWN:0,FLATE:1,LZW:2,DCT:3,JPX:4,JBIG:5,A85:6,AHX:7,CCF:8,RL:9},Q={UNKNOWN:0,TYPE1:1,TYPE1C:2,CIDFONTTYPE0:3,CIDFONTTYPE0C:4,TRUETYPE:5,CIDFONTTYPE2:6,TYPE3:7,OPENTYPE:8,TYPE0:9,MMTYPE1:10},$={errors:0,warnings:1,infos:5},ee={NONE:0,BINARY:1,STREAM:2},te={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotations:78,endAnnotations:79,beginAnnotation:80,endAnnotation:81,paintJpegXObject:82,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91},ae=$.warnings,re={unknown:"unknown",forms:"forms",javaScript:"javaScript",smask:"smask",shadingPattern:"shadingPattern",font:"font"},ie={NEED_PASSWORD:1,INCORRECT_PASSWORD:2},ne=function(){function e(e,t){this.name="PasswordException";this.message=e;this.code=t}e.prototype=new Error;e.constructor=e;return e}(),se=function(){function e(e,t){this.name="UnknownErrorException";this.message=e;this.details=t}e.prototype=new Error;e.constructor=e;return e}(),oe=function(){function e(e){this.name="InvalidPDFException";this.message=e}e.prototype=new Error;e.constructor=e;return e}(),ce=function(){function e(e){this.name="MissingPDFException";this.message=e}e.prototype=new Error;e.constructor=e;return e}(),le=function(){function e(e,t){this.name="UnexpectedResponseException";this.message=e;this.status=t}e.prototype=new Error;e.constructor=e;return e}(),he=function(){function e(e){this.message=e}e.prototype=new Error;e.prototype.name="NotImplementedException";e.constructor=e;return e}(),ue=function(){function e(e,t){this.begin=e;this.end=t;this.message="Missing data ["+e+", "+t+")"}e.prototype=new Error;e.prototype.name="MissingDataException";e.constructor=e;return e}(),fe=function(){function e(e){this.message=e}e.prototype=new Error;e.prototype.name="XRefParseException";e.constructor=e;return e}(),de=/\x00/g,ge=function(){function e(e,t){this.buffer=e;this.byteLength=e.length;this.length=void 0===t?this.byteLength>>2:t;a(this.length)}function t(e){return{get:function(){var t=this.buffer,a=e<<2;return(t[a]|t[a+1]<<8|t[a+2]<<16|t[a+3]<<24)>>>0},set:function(t){var a=this.buffer,r=e<<2;a[r]=255&t;a[r+1]=t>>8&255;a[r+2]=t>>16&255;a[r+3]=t>>>24&255}}}function a(a){for(;r<a;){Object.defineProperty(e.prototype,r,t(r));r++}}e.prototype=Object.create(null);var r=0;return e}();t.Uint32ArrayView=ge;var pe=[1,0,0,1,0,0],me=function(){function e(){}var t=["rgb(",0,",",0,",",0,")"];e.makeCssRgb=function(e,a,r){t[1]=e;t[3]=a;t[5]=r;return t.join("")};e.transform=function(e,t){return[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]]};e.applyTransform=function(e,t){return[e[0]*t[0]+e[1]*t[2]+t[4],e[0]*t[1]+e[1]*t[3]+t[5]]};e.applyInverseTransform=function(e,t){var a=t[0]*t[3]-t[1]*t[2];return[(e[0]*t[3]-e[1]*t[2]+t[2]*t[5]-t[4]*t[3])/a,(-e[0]*t[1]+e[1]*t[0]+t[4]*t[1]-t[5]*t[0])/a]};e.getAxialAlignedBoundingBox=function(t,a){var r=e.applyTransform(t,a),i=e.applyTransform(t.slice(2,4),a),n=e.applyTransform([t[0],t[3]],a),s=e.applyTransform([t[2],t[1]],a);return[Math.min(r[0],i[0],n[0],s[0]),Math.min(r[1],i[1],n[1],s[1]),Math.max(r[0],i[0],n[0],s[0]),Math.max(r[1],i[1],n[1],s[1])]};e.inverseTransform=function(e){var t=e[0]*e[3]-e[1]*e[2];return[e[3]/t,-e[1]/t,-e[2]/t,e[0]/t,(e[2]*e[5]-e[4]*e[3])/t,(e[4]*e[1]-e[5]*e[0])/t]};e.apply3dTransform=function(e,t){return[e[0]*t[0]+e[1]*t[1]+e[2]*t[2],e[3]*t[0]+e[4]*t[1]+e[5]*t[2],e[6]*t[0]+e[7]*t[1]+e[8]*t[2]]};e.singularValueDecompose2dScale=function(e){var t=[e[0],e[2],e[1],e[3]],a=e[0]*t[0]+e[1]*t[2],r=e[0]*t[1]+e[1]*t[3],i=e[2]*t[0]+e[3]*t[2],n=e[2]*t[1]+e[3]*t[3],s=(a+n)/2,o=Math.sqrt((a+n)*(a+n)-4*(a*n-i*r))/2,c=s+o||1,l=s-o||1;return[Math.sqrt(c),Math.sqrt(l)]};e.normalizeRect=function(e){var t=e.slice(0);if(e[0]>e[2]){t[0]=e[2];t[2]=e[0]}if(e[1]>e[3]){t[1]=e[3];t[3]=e[1]}return t};e.intersect=function(t,a){function r(e,t){return e-t}var i=[t[0],t[2],a[0],a[2]].sort(r),n=[t[1],t[3],a[1],a[3]].sort(r),s=[];t=e.normalizeRect(t);a=e.normalizeRect(a);if(!(i[0]===t[0]&&i[1]===a[0]||i[0]===a[0]&&i[1]===t[0]))return!1;s[0]=i[1];s[2]=i[2];if(!(n[0]===t[1]&&n[1]===a[1]||n[0]===a[1]&&n[1]===t[1]))return!1;s[1]=n[1];s[3]=n[2];return s};e.sign=function(e){return e<0?-1:1};var a=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"];e.toRoman=function(e,t){h(E(e)&&e>0,"The number should be a positive integer.");for(var r,i=[];e>=1e3;){e-=1e3;i.push("M")}r=e/100|0;e%=100;i.push(a[r]);r=e/10|0;e%=10;i.push(a[10+r]);i.push(a[20+e]);var n=i.join("");return t?n.toLowerCase():n};e.appendToArray=function(e,t){Array.prototype.push.apply(e,t)};e.prependToArray=function(e,t){Array.prototype.unshift.apply(e,t)};e.extendObj=function(e,t){for(var a in t)e[a]=t[a]};e.getInheritableProperty=function(e,t,a){for(;e&&!e.has(t);)e=e.get("Parent");return e?a?e.getArray(t):e.get(t):null};e.inherit=function(e,t,a){e.prototype=Object.create(t.prototype);e.prototype.constructor=e;for(var r in a)e.prototype[r]=a[r]};e.loadScript=function(e,t){var a=document.createElement("script"),r=!1;a.setAttribute("src",e);t&&(a.onload=function(){r||t();r=!0});document.getElementsByTagName("head")[0].appendChild(a)};return e}(),be=function(){function e(e,t,a,r,i,n){this.viewBox=e;this.scale=t;this.rotation=a;this.offsetX=r;this.offsetY=i;var s,o,c,l,h=(e[2]+e[0])/2,u=(e[3]+e[1])/2;a%=360;a=a<0?a+360:a;switch(a){case 180:s=-1;o=0;c=0;l=1;break;case 90:s=0;o=1;c=1;l=0;break;case 270:s=0;o=-1;c=-1;l=0;break;default:s=1;o=0;c=0;l=-1}if(n){c=-c;l=-l}var f,d,g,p;if(0===s){f=Math.abs(u-e[1])*t+r;d=Math.abs(h-e[0])*t+i;g=Math.abs(e[3]-e[1])*t;p=Math.abs(e[2]-e[0])*t}else{f=Math.abs(h-e[0])*t+r;d=Math.abs(u-e[1])*t+i;g=Math.abs(e[2]-e[0])*t;p=Math.abs(e[3]-e[1])*t}this.transform=[s*t,o*t,c*t,l*t,f-s*t*h-c*t*u,d-o*t*h-l*t*u];this.width=g;this.height=p;this.fontScale=t}e.prototype={clone:function(t){t=t||{};var a="scale"in t?t.scale:this.scale,r="rotation"in t?t.rotation:this.rotation;return new e(this.viewBox.slice(),a,r,this.offsetX,this.offsetY,t.dontFlip)},convertToViewportPoint:function(e,t){return me.applyTransform([e,t],this.transform)},convertToViewportRectangle:function(e){var t=me.applyTransform([e[0],e[1]],this.transform),a=me.applyTransform([e[2],e[3]],this.transform);return[t[0],t[1],a[0],a[1]]},convertToPdfPoint:function(e,t){return me.applyInverseTransform([e,t],this.transform)}};return e}(),ve=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],ye=function(){function e(e,t,a){for(;e.length<a;)e+=t;return e}function t(){this.started=Object.create(null);this.times=[];this.enabled=!0}t.prototype={time:function(e){if(this.enabled){e in this.started&&s("Timer is already running for "+e);this.started[e]=Date.now()}},timeEnd:function(e){if(this.enabled){e in this.started||s("Timer has not been started for "+e);this.times.push({name:e,start:this.started[e],end:Date.now()});delete this.started[e]}},toString:function(){var t,a,r=this.times,i="",n=0;for(t=0,a=r.length;t<a;++t){var s=r[t].name;s.length>n&&(n=s.length)}for(t=0,a=r.length;t<a;++t){var o=r[t],c=o.end-o.start;i+=e(o.name," ",n)+" "+c+"ms\n"}return i}};return t}(),ke=function(e,t){if("undefined"!=typeof Blob)return new Blob([e],{type:t});s('The "Blob" constructor is not supported.')},we=function(){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return function(t,a,r){if(!r&&"undefined"!=typeof URL&&URL.createObjectURL){var i=ke(t,a);return URL.createObjectURL(i)}for(var n="data:"+a+";base64,",s=0,o=t.length;s<o;s+=3){var c=255&t[s],l=255&t[s+1],h=255&t[s+2],u=c>>2,f=(3&c)<<4|l>>4,d=s+1<o?(15&l)<<2|h>>6:64,g=s+2<o?63&h:64;n+=e[u]+e[f]+e[d]+e[g]}return n}}();_.prototype={on:function(e,t,a){var r=this.actionHandler;r[e]&&c('There is already an actionName called "'+e+'"');r[e]=[t,a]},send:function(e,t,a){var r={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t};this.postMessage(r,a)},sendWithPromise:function(e,t,a){var r=this.callbackIndex++,i={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t,callbackId:r},n=j();this.callbacksCapabilities[r]=n;try{this.postMessage(i,a)}catch(e){n.reject(e)}return n.promise},postMessage:function(e,t){t&&this.postMessageTransfers?this.comObj.postMessage(e,t):this.comObj.postMessage(e)},destroy:function(){this.comObj.removeEventListener("message",this._onComObjOnMessage)}};t.FONT_IDENTITY_MATRIX=G;t.IDENTITY_MATRIX=pe;t.OPS=te;t.VERBOSITY_LEVELS=$;t.UNSUPPORTED_FEATURES=re;t.AnnotationBorderStyleType=J;t.AnnotationFieldFlag=Y;t.AnnotationFlag=K;t.AnnotationType=W;t.FontType=Q;t.ImageKind=V;t.CMapCompressionType=ee;t.InvalidPDFException=oe;t.MessageHandler=_;t.MissingDataException=ue;t.MissingPDFException=ce;t.NotImplementedException=he;t.PageViewport=be;t.PasswordException=ne;t.PasswordResponses=ie;t.StatTimer=ye;t.StreamType=Z;t.TextRenderingMode=X;t.UnexpectedResponseException=le;t.UnknownErrorException=se;t.Util=me;t.XRefParseException=fe;t.arrayByteLength=y;t.arraysToBytes=k;t.assert=h;t.bytesToString=b;t.createBlob=ke;t.createPromiseCapability=j;t.createObjectURL=we;t.deprecated=o;t.error=c;t.getLookupTableFactory=p;t.getVerbosityLevel=i;t.globalScope=H;t.info=n;t.isArray=F;t.isArrayBuffer=q;t.isBool=M;t.isEmptyObj=P;t.isInt=E;t.isNum=L;t.isString=D;t.isSpace=U;t.isNodeJS=N;t.isSameOrigin=u;t.createValidAbsoluteUrl=d;t.isLittleEndian=I;t.isEvalSupported=B;t.loadJpegStream=z;t.log2=C;t.readInt8=x;t.readUint16=S;t.readUint32=A;t.removeNullCharacters=m;t.setVerbosityLevel=r;t.shadow=g;t.string32=w;t.stringToBytes=v;t.stringToPDFString=R;t.stringToUTF8String=T;t.utf8StringToString=O;t.warn=s}).call(t,a(9))},function(e,t,a){"use strict";function r(e){return e===f}function i(e,t){return e instanceof d&&(void 0===t||e.name===t)}function n(e,t){return e instanceof g&&(void 0===t||e.cmd===t)}function s(e,t){return e instanceof p&&(void 0===t||i(e.get("Type"),t))}function o(e){return e instanceof m}function c(e,t){return e.num===t.num&&e.gen===t.gen}function l(e){return"object"==typeof e&&null!==e&&void 0!==e.getBytes}var h=a(0),u=h.isArray,f={},d=function(){function e(e){this.name=e}e.prototype={};var t=Object.create(null);e.get=function(a){var r=t[a];return r||(t[a]=new e(a))};return e}(),g=function(){function e(e){this.cmd=e}e.prototype={};var t=Object.create(null);e.get=function(a){var r=t[a];return r||(t[a]=new e(a))};return e}(),p=function(){function e(e){this.map=Object.create(null);this.xref=e;this.objId=null;this.suppressEncryption=!1;this.__nonSerializable__=t}var t=function(){return t};e.prototype={assignXref:function(e){this.xref=e},get:function(e,t,a){var r,i=this.xref,n=this.suppressEncryption;if(void 0!==(r=this.map[e])||e in this.map||void 0===t)return i?i.fetchIfRef(r,n):r;if(void 0!==(r=this.map[t])||t in this.map||void 0===a)return i?i.fetchIfRef(r,n):r;r=this.map[a]||null;return i?i.fetchIfRef(r,n):r},getAsync:function(e,t,a){var r,i=this.xref,n=this.suppressEncryption;if(void 0!==(r=this.map[e])||e in this.map||void 0===t)return i?i.fetchIfRefAsync(r,n):Promise.resolve(r);if(void 0!==(r=this.map[t])||t in this.map||void 0===a)return i?i.fetchIfRefAsync(r,n):Promise.resolve(r);r=this.map[a]||null;return i?i.fetchIfRefAsync(r,n):Promise.resolve(r)},getArray:function(e,t,a){var r=this.get(e,t,a),i=this.xref,n=this.suppressEncryption;if(!u(r)||!i)return r;r=r.slice();for(var s=0,c=r.length;s<c;s++)o(r[s])&&(r[s]=i.fetch(r[s],n));return r},getRaw:function(e){return this.map[e]},getKeys:function(){return Object.keys(this.map)},set:function(e,t){this.map[e]=t},has:function(e){return e in this.map},forEach:function(e){for(var t in this.map)e(t,this.get(t))}};e.empty=new e(null);e.merge=function(t,a){for(var r=new e(t),i=0,n=a.length;i<n;i++){var o=a[i];if(s(o))for(var c in o.map)r.map[c]||(r.map[c]=o.map[c])}return r};return e}(),m=function(){function e(e,t){this.num=e;this.gen=t}e.prototype={toString:function(){var e=this.num+"R";0!==this.gen&&(e+=this.gen);return e}};return e}(),b=function(){function e(){this.dict=Object.create(null)}e.prototype={has:function(e){return e.toString()in this.dict},put:function(e){this.dict[e.toString()]=!0},remove:function(e){delete this.dict[e.toString()]}};return e}(),v=function(){function e(){this.dict=Object.create(null)}e.prototype={get:function(e){return this.dict[e.toString()]},has:function(e){return e.toString()in this.dict},put:function(e,t){this.dict[e.toString()]=t},putAlias:function(e,t){this.dict[e.toString()]=this.get(t)},forEach:function(e,t){for(var a in this.dict)e.call(t,this.dict[a])},clear:function(){this.dict=Object.create(null)}};return e}();t.EOF=f;t.Cmd=g;t.Dict=p;t.Name=d;t.Ref=m;t.RefSet=b;t.RefSetCache=v;t.isEOF=r;t.isCmd=n;t.isDict=s;t.isName=i;t.isRef=o;t.isRefsEqual=c;t.isStream=l},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(28),s=a(29),o=a(15),c=r.Util,l=r.error,h=r.info,u=r.isInt,f=r.isArray,d=r.createObjectURL,g=r.shadow,p=r.isSpace,m=i.Dict,b=i.isDict,v=i.isStream,y=n.Jbig2Image,k=s.JpegImage,w=o.JpxImage,C=function(){function e(e,t,a,r){this.bytes=e instanceof Uint8Array?e:new Uint8Array(e);this.start=t||0;this.pos=this.start;this.end=t+a||this.bytes.length;this.dict=r}e.prototype={get length(){return this.end-this.start},get isEmpty(){return 0===this.length},getByte:function(){return this.pos>=this.end?-1:this.bytes[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t=this.bytes,a=this.pos,r=this.end;if(!e)return t.subarray(a,r);var i=a+e;i>r&&(i=r);this.pos=i;return t.subarray(a,i)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=this.start},moveStart:function(){this.start=this.pos},makeSubStream:function(t,a,r){return new e(this.bytes.buffer,t,a,r)}};return e}(),x=function(){function e(e){for(var t=e.length,a=new Uint8Array(t),r=0;r<t;++r)a[r]=e.charCodeAt(r);C.call(this,a)}e.prototype=C.prototype;return e}(),S=function(){function e(e){this.pos=0;this.bufferLength=0;this.eof=!1;this.buffer=t;this.minBufferLength=512;if(e)for(;this.minBufferLength<e;)this.minBufferLength*=2}var t=new Uint8Array(0);e.prototype={get isEmpty(){for(;!this.eof&&0===this.bufferLength;)this.readBlock();return 0===this.bufferLength},ensureBuffer:function(e){var t=this.buffer;if(e<=t.byteLength)return t;for(var a=this.minBufferLength;a<e;)a*=2;var r=new Uint8Array(a);r.set(t);return this.buffer=r},getByte:function(){for(var e=this.pos;this.bufferLength<=e;){if(this.eof)return-1;this.readBlock()}return this.buffer[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t,a=this.pos;if(e){this.ensureBuffer(a+e);t=a+e;for(;!this.eof&&this.bufferLength<t;)this.readBlock();var r=this.bufferLength;t>r&&(t=r)}else{for(;!this.eof;)this.readBlock();t=this.bufferLength}this.pos=t;return this.buffer.subarray(a,t)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},makeSubStream:function(e,t,a){for(var r=e+t;this.bufferLength<=r&&!this.eof;)this.readBlock();return new C(this.buffer,e,t,a)},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=0},getBaseStreams:function(){return this.str&&this.str.getBaseStreams?this.str.getBaseStreams():[]}};return e}(),A=function(){function e(e){this.streams=e;S.call(this,null)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.streams;if(0!==e.length){var t=e.shift(),a=t.getBytes(),r=this.bufferLength,i=r+a.length;this.ensureBuffer(i).set(a,r);this.bufferLength=i}else this.eof=!0};e.prototype.getBaseStreams=function(){for(var e=[],t=0,a=this.streams.length;t<a;t++){var r=this.streams[t];r.getBaseStreams&&c.appendToArray(e,r.getBaseStreams())}return e};return e}(),I=function(){function e(e,t){this.str=e;this.dict=e.dict;var a=e.getByte(),r=e.getByte();-1!==a&&-1!==r||l("Invalid header in flate stream: "+a+", "+r);8!=(15&a)&&l("Unknown compression method in flate stream: "+a+", "+r);((a<<8)+r)%31!=0&&l("Bad FCHECK in flate stream: "+a+", "+r);32&r&&l("FDICT bit set in flate stream: "+a+", "+r);this.codeSize=0;this.codeBuf=0;S.call(this,t)}var t=new Int32Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),a=new Int32Array([3,4,5,6,7,8,9,10,65547,65549,65551,65553,131091,131095,131099,131103,196643,196651,196659,196667,262211,262227,262243,262259,327811,327843,327875,327907,258,258,258]),r=new Int32Array([1,2,3,4,65541,65543,131081,131085,196625,196633,262177,262193,327745,327777,393345,393409,459009,459137,524801,525057,590849,591361,657409,658433,724993,727041,794625,798721,868353,876545]),i=[new Int32Array([459008,524368,524304,524568,459024,524400,524336,590016,459016,524384,524320,589984,524288,524416,524352,590048,459012,524376,524312,589968,459028,524408,524344,590032,459020,524392,524328,59e4,524296,524424,524360,590064,459010,524372,524308,524572,459026,524404,524340,590024,459018,524388,524324,589992,524292,524420,524356,590056,459014,524380,524316,589976,459030,524412,524348,590040,459022,524396,524332,590008,524300,524428,524364,590072,459009,524370,524306,524570,459025,524402,524338,590020,459017,524386,524322,589988,524290,524418,524354,590052,459013,524378,524314,589972,459029,524410,524346,590036,459021,524394,524330,590004,524298,524426,524362,590068,459011,524374,524310,524574,459027,524406,524342,590028,459019,524390,524326,589996,524294,524422,524358,590060,459015,524382,524318,589980,459031,524414,524350,590044,459023,524398,524334,590012,524302,524430,524366,590076,459008,524369,524305,524569,459024,524401,524337,590018,459016,524385,524321,589986,524289,524417,524353,590050,459012,524377,524313,589970,459028,524409,524345,590034,459020,524393,524329,590002,524297,524425,524361,590066,459010,524373,524309,524573,459026,524405,524341,590026,459018,524389,524325,589994,524293,524421,524357,590058,459014,524381,524317,589978,459030,524413,524349,590042,459022,524397,524333,590010,524301,524429,524365,590074,459009,524371,524307,524571,459025,524403,524339,590022,459017,524387,524323,589990,524291,524419,524355,590054,459013,524379,524315,589974,459029,524411,524347,590038,459021,524395,524331,590006,524299,524427,524363,590070,459011,524375,524311,524575,459027,524407,524343,590030,459019,524391,524327,589998,524295,524423,524359,590062,459015,524383,524319,589982,459031,524415,524351,590046,459023,524399,524335,590014,524303,524431,524367,590078,459008,524368,524304,524568,459024,524400,524336,590017,459016,524384,524320,589985,524288,524416,524352,590049,459012,524376,524312,589969,459028,524408,524344,590033,459020,524392,524328,590001,524296,524424,524360,590065,459010,524372,524308,524572,459026,524404,524340,590025,459018,524388,524324,589993,524292,524420,524356,590057,459014,524380,524316,589977,459030,524412,524348,590041,459022,524396,524332,590009,524300,524428,524364,590073,459009,524370,524306,524570,459025,524402,524338,590021,459017,524386,524322,589989,524290,524418,524354,590053,459013,524378,524314,589973,459029,524410,524346,590037,459021,524394,524330,590005,524298,524426,524362,590069,459011,524374,524310,524574,459027,524406,524342,590029,459019,524390,524326,589997,524294,524422,524358,590061,459015,524382,524318,589981,459031,524414,524350,590045,459023,524398,524334,590013,524302,524430,524366,590077,459008,524369,524305,524569,459024,524401,524337,590019,459016,524385,524321,589987,524289,524417,524353,590051,459012,524377,524313,589971,459028,524409,524345,590035,459020,524393,524329,590003,524297,524425,524361,590067,459010,524373,524309,524573,459026,524405,524341,590027,459018,524389,524325,589995,524293,524421,524357,590059,459014,524381,524317,589979,459030,524413,524349,590043,459022,524397,524333,590011,524301,524429,524365,590075,459009,524371,524307,524571,459025,524403,524339,590023,459017,524387,524323,589991,524291,524419,524355,590055,459013,524379,524315,589975,459029,524411,524347,590039,459021,524395,524331,590007,524299,524427,524363,590071,459011,524375,524311,524575,459027,524407,524343,590031,459019,524391,524327,589999,524295,524423,524359,590063,459015,524383,524319,589983,459031,524415,524351,590047,459023,524399,524335,590015,524303,524431,524367,590079]),9],n=[new Int32Array([327680,327696,327688,327704,327684,327700,327692,327708,327682,327698,327690,327706,327686,327702,327694,0,327681,327697,327689,327705,327685,327701,327693,327709,327683,327699,327691,327707,327687,327703,327695,0]),5];e.prototype=Object.create(S.prototype);e.prototype.getBits=function(e){for(var t,a=this.str,r=this.codeSize,i=this.codeBuf;r<e;){-1===(t=a.getByte())&&l("Bad encoding in flate stream");i|=t<<r;r+=8}t=i&(1<<e)-1;this.codeBuf=i>>e;this.codeSize=r-=e;return t};e.prototype.getCode=function(e){for(var t,a=this.str,r=e[0],i=e[1],n=this.codeSize,s=this.codeBuf;n<i&&-1!==(t=a.getByte());){s|=t<<n;n+=8}var o=r[s&(1<<i)-1],c=o>>16,h=65535&o;(c<1||n<c)&&l("Bad encoding in flate stream");this.codeBuf=s>>c;this.codeSize=n-c;return h};e.prototype.generateHuffmanTable=function(e){var t,a=e.length,r=0;for(t=0;t<a;++t)e[t]>r&&(r=e[t]);for(var i=1<<r,n=new Int32Array(i),s=1,o=0,c=2;s<=r;++s,o<<=1,c<<=1)for(var l=0;l<a;++l)if(e[l]===s){var h=0,u=o;for(t=0;t<s;++t){h=h<<1|1&u;u>>=1}for(t=h;t<i;t+=c)n[t]=s<<16|l;++o}return[n,r]};e.prototype.readBlock=function(){var e,s,o=this.str,c=this.getBits(3);1&c&&(this.eof=!0);c>>=1;if(0!==c){var h,u;if(1===c){h=i;u=n}else if(2===c){var f,d=this.getBits(5)+257,g=this.getBits(5)+1,p=this.getBits(4)+4,m=new Uint8Array(t.length);for(f=0;f<p;++f)m[t[f]]=this.getBits(3);var b=this.generateHuffmanTable(m);s=0;f=0;for(var v,y,k,w=d+g,C=new Uint8Array(w);f<w;){var x=this.getCode(b);if(16===x){v=2;y=3;k=s}else if(17===x){v=3;y=3;k=s=0}else{if(18!==x){C[f++]=s=x;continue}v=7;y=11;k=s=0}for(var S=this.getBits(v)+y;S-- >0;)C[f++]=k}h=this.generateHuffmanTable(C.subarray(0,d));u=this.generateHuffmanTable(C.subarray(d,w))}else l("Unknown block type in flate stream");e=this.buffer;for(var A=e?e.length:0,I=this.bufferLength;;){var B=this.getCode(h);if(B<256){if(I+1>=A){e=this.ensureBuffer(I+1);A=e.length}e[I++]=B}else{if(256===B){this.bufferLength=I;return}B-=257;B=a[B];var R=B>>16;R>0&&(R=this.getBits(R));s=(65535&B)+R;B=this.getCode(u);B=r[B];R=B>>16;R>0&&(R=this.getBits(R));var T=(65535&B)+R;if(I+s>=A){e=this.ensureBuffer(I+s);A=e.length}for(var O=0;O<s;++O,++I)e[I]=e[I-T]}}}else{var P;-1===(P=o.getByte())&&l("Bad block header in flate stream");var M=P;-1===(P=o.getByte())&&l("Bad block header in flate stream");M|=P<<8;-1===(P=o.getByte())&&l("Bad block header in flate stream");var E=P;-1===(P=o.getByte())&&l("Bad block header in flate stream");E|=P<<8;E===(65535&~M)||0===M&&0===E||l("Bad uncompressed block length in flate stream");this.codeBuf=0;this.codeSize=0;var L=this.bufferLength;e=this.ensureBuffer(L+M);var D=L+M;this.bufferLength=D;if(0===M)-1===o.peekByte()&&(this.eof=!0);else for(var F=L;F<D;++F){if(-1===(P=o.getByte())){this.eof=!0;break}e[F]=P}}};return e}(),B=function(){function e(e,t,a){if(!b(a))return e;var r=this.predictor=a.get("Predictor")||1;if(r<=1)return e;2!==r&&(r<10||r>15)&&l("Unsupported predictor: "+r);this.readBlock=2===r?this.readBlockTiff:this.readBlockPng;this.str=e;this.dict=e.dict;var i=this.colors=a.get("Colors")||1,n=this.bits=a.get("BitsPerComponent")||8,s=this.columns=a.get("Columns")||1;this.pixBytes=i*n+7>>3;this.rowBytes=s*i*n+7>>3;S.call(this,t);return this}e.prototype=Object.create(S.prototype) +;e.prototype.readBlockTiff=function(){var e=this.rowBytes,t=this.bufferLength,a=this.ensureBuffer(t+e),r=this.bits,i=this.colors,n=this.str.getBytes(e);this.eof=!n.length;if(!this.eof){var s,o=0,c=0,l=0,h=0,u=t;if(1===r&&1===i)for(s=0;s<e;++s){var f=n[s]^o;f^=f>>1;f^=f>>2;f^=f>>4;o=(1&f)<<7;a[u++]=f}else if(8===r){for(s=0;s<i;++s)a[u++]=n[s];for(;s<e;++s){a[u]=a[u-i]+n[s];u++}}else{var d=new Uint8Array(i+1),g=(1<<r)-1,p=0,m=t,b=this.columns;for(s=0;s<b;++s)for(var v=0;v<i;++v){if(l<r){o=o<<8|255&n[p++];l+=8}d[v]=d[v]+(o>>l-r)&g;l-=r;c=c<<r|d[v];h+=r;if(h>=8){a[m++]=c>>h-8&255;h-=8}}h>0&&(a[m++]=(c<<8-h)+(o&(1<<8-h)-1))}this.bufferLength+=e}};e.prototype.readBlockPng=function(){var e=this.rowBytes,t=this.pixBytes,a=this.str.getByte(),r=this.str.getBytes(e);this.eof=!r.length;if(!this.eof){var i=this.bufferLength,n=this.ensureBuffer(i+e),s=n.subarray(i-e,i);0===s.length&&(s=new Uint8Array(e));var o,c,h,u=i;switch(a){case 0:for(o=0;o<e;++o)n[u++]=r[o];break;case 1:for(o=0;o<t;++o)n[u++]=r[o];for(;o<e;++o){n[u]=n[u-t]+r[o]&255;u++}break;case 2:for(o=0;o<e;++o)n[u++]=s[o]+r[o]&255;break;case 3:for(o=0;o<t;++o)n[u++]=(s[o]>>1)+r[o];for(;o<e;++o){n[u]=(s[o]+n[u-t]>>1)+r[o]&255;u++}break;case 4:for(o=0;o<t;++o){c=s[o];h=r[o];n[u++]=c+h}for(;o<e;++o){c=s[o];var f=s[o-t],d=n[u-t],g=d+c-f,p=g-d;p<0&&(p=-p);var m=g-c;m<0&&(m=-m);var b=g-f;b<0&&(b=-b);h=r[o];n[u++]=p<=m&&p<=b?d+h:m<=b?c+h:f+h}break;default:l("Unsupported predictor: "+a)}this.bufferLength+=e}};return e}(),R=function(){function e(e,t,a,r){for(var i;-1!==(i=e.getByte());)if(255===i){e.skip(-1);break}this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new k,a=this.dict.getArray("Decode","D");if(this.forceRGB&&f(a)){for(var r=this.dict.get("BitsPerComponent")||8,i=a.length,n=new Int32Array(i),s=!1,o=(1<<r)-1,c=0;c<i;c+=2){n[c]=256*(a[c+1]-a[c])|0;n[c+1]=a[c]*o|0;256===n[c]&&0===n[c+1]||(s=!0)}s&&(t.decodeTransform=n)}if(b(this.params)){var l=this.params.get("ColorTransform");u(l)&&(t.colorTransform=l)}t.parse(this.bytes);var h=t.getData(this.drawWidth,this.drawHeight,this.forceRGB);this.buffer=h;this.bufferLength=h.length;this.eof=!0}};e.prototype.getBytes=function(e){this.ensureBuffer();return this.buffer};e.prototype.getIR=function(e){return d(this.bytes,"image/jpeg",e)};return e}(),T=function(){function e(e,t,a,r){this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new w;t.parse(this.bytes);var a=t.width,r=t.height,i=t.componentsCount,n=t.tiles.length;if(1===n)this.buffer=t.tiles[0].items;else{for(var s=new Uint8Array(a*r*i),o=0;o<n;o++)for(var c=t.tiles[o],l=c.width,h=c.height,u=c.left,f=c.top,d=c.items,g=0,p=(a*f+u)*i,m=a*i,b=l*i,v=0;v<h;v++){var y=d.subarray(g,g+b);s.set(y,p);g+=b;p+=m}this.buffer=s}this.bufferLength=this.buffer.length;this.eof=!0}};return e}(),O=function(){function e(e,t,a,r){this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new y,a=[];if(b(this.params)){var r=this.params.get("JBIG2Globals");if(v(r)){var i=r.getBytes();a.push({data:i,start:0,end:i.length})}}a.push({data:this.bytes,start:0,end:this.bytes.length});for(var n=t.parseChunks(a),s=n.length,o=0;o<s;o++)n[o]^=255;this.buffer=n;this.bufferLength=s;this.eof=!0}};return e}(),P=function(){function e(e,t,a){this.str=e;this.dict=e.dict;this.decrypt=a;this.nextChunk=null;this.initialized=!1;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e;if(this.initialized)e=this.nextChunk;else{e=this.str.getBytes(512);this.initialized=!0}if(e&&0!==e.length){this.nextChunk=this.str.getBytes(512);var t=this.nextChunk&&this.nextChunk.length>0;e=(0,this.decrypt)(e,!t);var a,r=this.bufferLength,i=e.length,n=this.ensureBuffer(r+i);for(a=0;a<i;a++)n[r++]=e[a];this.bufferLength=r}else this.eof=!0};return e}(),M=function(){function e(e,t){this.str=e;this.dict=e.dict;this.input=new Uint8Array(5);t&&(t*=.8);S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){for(var e=this.str,t=e.getByte();p(t);)t=e.getByte();if(-1!==t&&126!==t){var a,r,i=this.bufferLength;if(122===t){a=this.ensureBuffer(i+4);for(r=0;r<4;++r)a[i+r]=0;this.bufferLength+=4}else{var n=this.input;n[0]=t;for(r=1;r<5;++r){t=e.getByte();for(;p(t);)t=e.getByte();n[r]=t;if(-1===t||126===t)break}a=this.ensureBuffer(i+r-1);this.bufferLength+=r-1;if(r<5){for(;r<5;++r)n[r]=117;this.eof=!0}var s=0;for(r=0;r<5;++r)s=85*s+(n[r]-33);for(r=3;r>=0;--r){a[i+r]=255&s;s>>=8}}}else this.eof=!0};return e}(),E=function(){function e(e,t){this.str=e;this.dict=e.dict;this.firstDigit=-1;t&&(t*=.5);S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.str.getBytes(8e3);if(e.length){for(var t=e.length+1>>1,a=this.ensureBuffer(this.bufferLength+t),r=this.bufferLength,i=this.firstDigit,n=0,s=e.length;n<s;n++){var o,c=e[n];if(c>=48&&c<=57)o=15&c;else{if(!(c>=65&&c<=70||c>=97&&c<=102)){if(62===c){this.eof=!0;break}continue}o=9+(15&c)}if(i<0)i=o;else{a[r++]=i<<4|o;i=-1}}if(i>=0&&this.eof){a[r++]=i<<4;i=-1}this.firstDigit=i;this.bufferLength=r}else this.eof=!0};return e}(),L=function(){function e(e,t){this.str=e;this.dict=e.dict;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.str.getBytes(2);if(!e||e.length<2||128===e[0])this.eof=!0;else{var t,a=this.bufferLength,r=e[0];if(r<128){t=this.ensureBuffer(a+r+1);t[a++]=e[1];if(r>0){var i=this.str.getBytes(r);t.set(i,a);a+=r}}else{r=257-r;var n=e[1];t=this.ensureBuffer(a+r+1);for(var s=0;s<r;s++)t[a++]=n}this.bufferLength=a}};return e}(),D=function(){function e(e,t,a){this.str=e;this.dict=e.dict;a=a||m.empty;this.encoding=a.get("K")||0;this.eoline=a.get("EndOfLine")||!1;this.byteAlign=a.get("EncodedByteAlign")||!1;this.columns=a.get("Columns")||1728;this.rows=a.get("Rows")||0;var r=a.get("EndOfBlock");null!==r&&void 0!==r||(r=!0);this.eoblock=r;this.black=a.get("BlackIs1")||!1;this.codingLine=new Uint32Array(this.columns+1);this.refLine=new Uint32Array(this.columns+2);this.codingLine[0]=this.columns;this.codingPos=0;this.row=0;this.nextLine2D=this.encoding<0;this.inputBits=0;this.inputBuf=0;this.outputBits=0;for(var i;0===(i=this.lookBits(12));)this.eatBits(1);1===i&&this.eatBits(12);if(this.encoding>0){this.nextLine2D=!this.lookBits(1);this.eatBits(1)}S.call(this,t)}var t=[[-1,-1],[-1,-1],[7,8],[7,7],[6,6],[6,6],[6,5],[6,5],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2]],a=[[-1,-1],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[12,1984],[12,2048],[12,2112],[12,2176],[12,2240],[12,2304],[11,1856],[11,1856],[11,1920],[11,1920],[12,2368],[12,2432],[12,2496],[12,2560]],r=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[8,29],[8,29],[8,30],[8,30],[8,45],[8,45],[8,46],[8,46],[7,22],[7,22],[7,22],[7,22],[7,23],[7,23],[7,23],[7,23],[8,47],[8,47],[8,48],[8,48],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[7,20],[7,20],[7,20],[7,20],[8,33],[8,33],[8,34],[8,34],[8,35],[8,35],[8,36],[8,36],[8,37],[8,37],[8,38],[8,38],[7,19],[7,19],[7,19],[7,19],[8,31],[8,31],[8,32],[8,32],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[8,53],[8,53],[8,54],[8,54],[7,26],[7,26],[7,26],[7,26],[8,39],[8,39],[8,40],[8,40],[8,41],[8,41],[8,42],[8,42],[8,43],[8,43],[8,44],[8,44],[7,21],[7,21],[7,21],[7,21],[7,28],[7,28],[7,28],[7,28],[8,61],[8,61],[8,62],[8,62],[8,63],[8,63],[8,0],[8,0],[8,320],[8,320],[8,384],[8,384],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[7,27],[7,27],[7,27],[7,27],[8,59],[8,59],[8,60],[8,60],[9,1472],[9,1536],[9,1600],[9,1728],[7,18],[7,18],[7,18],[7,18],[7,24],[7,24],[7,24],[7,24],[8,49],[8,49],[8,50],[8,50],[8,51],[8,51],[8,52],[8,52],[7,25],[7,25],[7,25],[7,25],[8,55],[8,55],[8,56],[8,56],[8,57],[8,57],[8,58],[8,58],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[8,448],[8,448],[8,512],[8,512],[9,704],[9,768],[8,640],[8,640],[8,576],[8,576],[9,832],[9,896],[9,960],[9,1024],[9,1088],[9,1152],[9,1216],[9,1280],[9,1344],[9,1408],[7,256],[7,256],[7,256],[7,256],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7]],i=[[-1,-1],[-1,-1],[12,-2],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[11,1792],[11,1792],[12,1984],[12,1984],[12,2048],[12,2048],[12,2112],[12,2112],[12,2176],[12,2176],[12,2240],[12,2240],[12,2304],[12,2304],[11,1856],[11,1856],[11,1856],[11,1856],[11,1920],[11,1920],[11,1920],[11,1920],[12,2368],[12,2368],[12,2432],[12,2432],[12,2496],[12,2496],[12,2560],[12,2560],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[12,52],[12,52],[13,640],[13,704],[13,768],[13,832],[12,55],[12,55],[12,56],[12,56],[13,1280],[13,1344],[13,1408],[13,1472],[12,59],[12,59],[12,60],[12,60],[13,1536],[13,1600],[11,24],[11,24],[11,24],[11,24],[11,25],[11,25],[11,25],[11,25],[13,1664],[13,1728],[12,320],[12,320],[12,384],[12,384],[12,448],[12,448],[13,512],[13,576],[12,53],[12,53],[12,54],[12,54],[13,896],[13,960],[13,1024],[13,1088],[13,1152],[13,1216],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64]],n=[[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[11,23],[11,23],[12,50],[12,51],[12,44],[12,45],[12,46],[12,47],[12,57],[12,58],[12,61],[12,256],[10,16],[10,16],[10,16],[10,16],[10,17],[10,17],[10,17],[10,17],[12,48],[12,49],[12,62],[12,63],[12,30],[12,31],[12,32],[12,33],[12,40],[12,41],[11,22],[11,22],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[12,128],[12,192],[12,26],[12,27],[12,28],[12,29],[11,19],[11,19],[11,20],[11,20],[12,34],[12,35],[12,36],[12,37],[12,38],[12,39],[11,21],[11,21],[12,42],[12,43],[10,0],[10,0],[10,0],[10,0],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12]],s=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[6,9],[6,8],[5,7],[5,7],[4,6],[4,6],[4,6],[4,6],[4,5],[4,5],[4,5],[4,5],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2]];e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){for(;!this.eof;){var e=this.lookChar();this.ensureBuffer(this.bufferLength+1);this.buffer[this.bufferLength++]=e}};e.prototype.addPixels=function(e,t){var a=this.codingLine,r=this.codingPos;if(e>a[r]){if(e>this.columns){h("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}this.codingPos=r};e.prototype.addPixelsNeg=function(e,t){var a=this.codingLine,r=this.codingPos;if(e>a[r]){if(e>this.columns){h("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}else if(e<a[r]){if(e<0){h("invalid code");this.err=!0;e=0}for(;r>0&&e<a[r-1];)--r;a[r]=e}this.codingPos=r};e.prototype.lookChar=function(){var e,t,a,r,i=this.refLine,n=this.codingLine,s=this.columns;if(0===this.outputBits){if(this.eof)return null;this.err=!1;var o,c,l;if(this.nextLine2D){for(r=0;n[r]<s;++r)i[r]=n[r];i[r++]=s;i[r]=s;n[0]=0;this.codingPos=0;e=0;t=0;for(;n[this.codingPos]<s;){o=this.getTwoDimCode();switch(o){case 0:this.addPixels(i[e+1],t);i[e+1]<s&&(e+=2);break;case 1:o=c=0;if(t){do{o+=l=this.getBlackCode()}while(l>=64);do{c+=l=this.getWhiteCode()}while(l>=64)}else{do{o+=l=this.getWhiteCode()}while(l>=64);do{c+=l=this.getBlackCode()}while(l>=64)}this.addPixels(n[this.codingPos]+o,t);n[this.codingPos]<s&&this.addPixels(n[this.codingPos]+c,1^t);for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2;break;case 7:this.addPixels(i[e]+3,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 5:this.addPixels(i[e]+2,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 3:this.addPixels(i[e]+1,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 2:this.addPixels(i[e],t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 8:this.addPixelsNeg(i[e]-3,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 6:this.addPixelsNeg(i[e]-2,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 4:this.addPixelsNeg(i[e]-1,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case-1:this.addPixels(s,0);this.eof=!0;break;default:h("bad 2d code");this.addPixels(s,0);this.err=!0}}}else{n[0]=0;this.codingPos=0;t=0;for(;n[this.codingPos]<s;){o=0;if(t)do{o+=l=this.getBlackCode()}while(l>=64);else do{o+=l=this.getWhiteCode()}while(l>=64);this.addPixels(n[this.codingPos]+o,t);t^=1}}var u=!1;this.byteAlign&&(this.inputBits&=-8);if(this.eoblock||this.row!==this.rows-1){o=this.lookBits(12);if(this.eoline)for(;-1!==o&&1!==o;){this.eatBits(1);o=this.lookBits(12)}else for(;0===o;){this.eatBits(1);o=this.lookBits(12)}if(1===o){this.eatBits(12);u=!0}else-1===o&&(this.eof=!0)}else this.eof=!0;if(!this.eof&&this.encoding>0){this.nextLine2D=!this.lookBits(1);this.eatBits(1)}if(this.eoblock&&u&&this.byteAlign){o=this.lookBits(12);if(1===o){this.eatBits(12);if(this.encoding>0){this.lookBits(1);this.eatBits(1)}if(this.encoding>=0)for(r=0;r<4;++r){o=this.lookBits(12);1!==o&&h("bad rtc code: "+o);this.eatBits(12);if(this.encoding>0){this.lookBits(1);this.eatBits(1)}}this.eof=!0}}else if(this.err&&this.eoline){for(;;){o=this.lookBits(13);if(-1===o){this.eof=!0;return null}if(o>>1==1)break;this.eatBits(1)}this.eatBits(12);if(this.encoding>0){this.eatBits(1);this.nextLine2D=!(1&o)}}n[0]>0?this.outputBits=n[this.codingPos=0]:this.outputBits=n[this.codingPos=1];this.row++}var f;if(this.outputBits>=8){f=1&this.codingPos?0:255;this.outputBits-=8;if(0===this.outputBits&&n[this.codingPos]<s){this.codingPos++;this.outputBits=n[this.codingPos]-n[this.codingPos-1]}}else{a=8;f=0;do{if(this.outputBits>a){f<<=a;1&this.codingPos||(f|=255>>8-a);this.outputBits-=a;a=0}else{f<<=this.outputBits;1&this.codingPos||(f|=255>>8-this.outputBits);a-=this.outputBits;this.outputBits=0;if(n[this.codingPos]<s){this.codingPos++;this.outputBits=n[this.codingPos]-n[this.codingPos-1]}else if(a>0){f<<=a;a=0}}}while(a)}this.black&&(f^=255);return f};e.prototype.findTableCode=function(e,t,a,r){for(var i=r||0,n=e;n<=t;++n){var s=this.lookBits(n);if(-1===s)return[!0,1,!1];n<t&&(s<<=t-n);if(!i||s>=i){var o=a[s-i];if(o[0]===n){this.eatBits(n);return[!0,o[1],!0]}}}return[!1,0,!1]};e.prototype.getTwoDimCode=function(){var e,a=0;if(this.eoblock){a=this.lookBits(7);e=t[a];if(e&&e[0]>0){this.eatBits(e[0]);return e[1]}}else{var r=this.findTableCode(1,7,t);if(r[0]&&r[2])return r[1]}h("Bad two dim code");return-1};e.prototype.getWhiteCode=function(){var e,t=0;if(this.eoblock){t=this.lookBits(12);if(-1===t)return 1;e=t>>5==0?a[t]:r[t>>3];if(e[0]>0){this.eatBits(e[0]);return e[1]}}else{var i=this.findTableCode(1,9,r);if(i[0])return i[1];i=this.findTableCode(11,12,a);if(i[0])return i[1]}h("bad white code");this.eatBits(1);return 1};e.prototype.getBlackCode=function(){var e,t;if(this.eoblock){e=this.lookBits(13);if(-1===e)return 1;t=e>>7==0?i[e]:e>>9==0&&e>>7!=0?n[(e>>1)-64]:s[e>>7];if(t[0]>0){this.eatBits(t[0]);return t[1]}}else{var a=this.findTableCode(2,6,s);if(a[0])return a[1];a=this.findTableCode(7,12,n,64);if(a[0])return a[1];a=this.findTableCode(10,13,i);if(a[0])return a[1]}h("bad black code");this.eatBits(1);return 1};e.prototype.lookBits=function(e){for(var t;this.inputBits<e;){if(-1===(t=this.str.getByte()))return 0===this.inputBits?-1:this.inputBuf<<e-this.inputBits&65535>>16-e;this.inputBuf=this.inputBuf<<8|t;this.inputBits+=8}return this.inputBuf>>this.inputBits-e&65535>>16-e};e.prototype.eatBits=function(e){(this.inputBits-=e)<0&&(this.inputBits=0)};return e}(),F=function(){function e(e,t,a){this.str=e;this.dict=e.dict;this.cachedData=0;this.bitsCached=0;for(var r={earlyChange:a,codeLength:9,nextCode:258,dictionaryValues:new Uint8Array(4096),dictionaryLengths:new Uint16Array(4096),dictionaryPrevCodes:new Uint16Array(4096),currentSequence:new Uint8Array(4096),currentSequenceLength:0},i=0;i<256;++i){r.dictionaryValues[i]=i;r.dictionaryLengths[i]=1}this.lzwState=r;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBits=function(e){for(var t=this.bitsCached,a=this.cachedData;t<e;){var r=this.str.getByte();if(-1===r){this.eof=!0;return null}a=a<<8|r;t+=8}this.bitsCached=t-=e;this.cachedData=a;this.lastCode=null;return a>>>t&(1<<e)-1};e.prototype.readBlock=function(){var e,t,a,r=1024,i=this.lzwState;if(i){var n=i.earlyChange,s=i.nextCode,o=i.dictionaryValues,c=i.dictionaryLengths,l=i.dictionaryPrevCodes,h=i.codeLength,u=i.prevCode,f=i.currentSequence,d=i.currentSequenceLength,g=0,p=this.bufferLength,m=this.ensureBuffer(this.bufferLength+r);for(e=0;e<512;e++){var b=this.readBits(h),v=d>0;if(b<256){f[0]=b;d=1}else{if(!(b>=258)){if(256===b){h=9;s=258;d=0;continue}this.eof=!0;delete this.lzwState;break}if(b<s){d=c[b];for(t=d-1,a=b;t>=0;t--){f[t]=o[a];a=l[a]}}else f[d++]=f[0]}if(v){l[s]=u;c[s]=c[u]+1;o[s]=f[0];s++;h=s+n&s+n-1?h:0|Math.min(Math.log(s+n)/.6931471805599453+1,12)}u=b;g+=d;if(r<g){do{r+=512}while(r<g);m=this.ensureBuffer(this.bufferLength+r)}for(t=0;t<d;t++)m[p++]=f[t]}i.nextCode=s;i.codeLength=h;i.prevCode=u;i.currentSequenceLength=d;this.bufferLength=p}};return e}(),q=function(){function e(){C.call(this,new Uint8Array(0))}e.prototype=C.prototype;return e}();t.Ascii85Stream=M;t.AsciiHexStream=E;t.CCITTFaxStream=D;t.DecryptStream=P;t.DecodeStream=S;t.FlateStream=I;t.Jbig2Stream=O;t.JpegStream=R;t.JpxStream=T;t.NullStream=q;t.PredictorStream=B;t.RunLengthStream=L;t.Stream=C;t.StreamsSequenceStream=A;t.StringStream=x;t.LZWStream=F},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(6),s=r.error,o=r.info,c=r.isArray,l=r.isString,h=r.shadow,u=r.warn,f=i.isDict,d=i.isName,g=i.isStream,p=n.PDFFunction,m=function(){function e(e,t,a,r,i,n,s,o){s=1!==s?0:s;var c,l,h,u,f=a/i,d=r/n,g=0,p=new Uint16Array(i),m=3*a;for(c=0;c<i;c++)p[c]=3*Math.floor(c*f);for(c=0;c<n;c++){h=Math.floor(c*d)*m;for(l=0;l<i;l++){u=h+p[l];o[g++]=e[u++];o[g++]=e[u++];o[g++]=e[u++];g+=s}}}function t(){s("should not call ColorSpace constructor")}t.prototype={getRgb:function(e,t){var a=new Uint8Array(3);this.getRgbItem(e,t,a,0);return a},getRgbItem:function(e,t,a,r){s("Should not call ColorSpace.getRgbItem")},getRgbBuffer:function(e,t,a,r,i,n,o){s("Should not call ColorSpace.getRgbBuffer")},getOutputLength:function(e,t){s("Should not call ColorSpace.getOutputLength")},isPassthrough:function(e){return!1},fillRgb:function(t,a,r,i,n,s,o,c,l){var h,u,f=a*r,d=null,g=1<<o,p=r!==n||a!==i;if(this.isPassthrough(o))d=c;else if(1===this.numComps&&f>g&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){var m,b=o<=8?new Uint8Array(g):new Uint16Array(g);for(h=0;h<g;h++)b[h]=h;var v=new Uint8Array(3*g);this.getRgbBuffer(b,0,g,v,0,o,0);var y,k;if(p){d=new Uint8Array(3*f);k=0;for(h=0;h<f;++h){m=3*c[h];d[k++]=v[m];d[k++]=v[m+1];d[k++]=v[m+2]}}else{y=0;for(h=0;h<f;++h){m=3*c[h];t[y++]=v[m];t[y++]=v[m+1];t[y++]=v[m+2];y+=l}}}else if(p){d=new Uint8Array(3*f);this.getRgbBuffer(c,0,f,d,0,o,0)}else this.getRgbBuffer(c,0,i*s,t,0,o,l);if(d)if(p)e(d,o,a,r,i,n,l,t);else{k=0;y=0;for(h=0,u=i*s;h<u;h++){t[y++]=d[k++];t[y++]=d[k++];t[y++]=d[k++];y+=l}}},usesZeroToOneRange:!0};t.parse=function(e,a,r){var i=t.parseToIR(e,a,r);return i instanceof b?i:t.fromIR(i)};t.fromIR=function(e){var a,r,i,n=c(e)?e[0]:e;switch(n){case"DeviceGrayCS":return this.singletons.gray;case"DeviceRgbCS":return this.singletons.rgb;case"DeviceCmykCS":return this.singletons.cmyk;case"CalGrayCS":a=e[1];r=e[2];i=e[3];return new x(a,r,i);case"CalRGBCS":a=e[1];r=e[2];i=e[3];var o=e[4];return new S(a,r,i,o);case"PatternCS":var l=e[1];l&&(l=t.fromIR(l));return new v(l);case"IndexedCS":var h=e[1],u=e[2],f=e[3];return new y(t.fromIR(h),u,f);case"AlternateCS":var d=e[1],g=e[2],m=e[3];return new b(d,t.fromIR(g),p.fromIR(m));case"LabCS":a=e[1];r=e[2];var k=e[3];return new A(a,r,k);default:s("Unknown name "+n)}return null};t.parseToIR=function(e,a,r){if(d(e)){var i=r.get("ColorSpace");if(f(i)){var n=i.get(e.name);n&&(e=n)}}e=a.fetchIfRef(e);if(d(e))switch(e.name){case"DeviceGray":case"G":return"DeviceGrayCS";case"DeviceRGB":case"RGB":return"DeviceRgbCS";case"DeviceCMYK":case"CMYK":return"DeviceCmykCS";case"Pattern":return["PatternCS",null];default:s("unrecognized colorspace "+e.name)}else if(c(e)){var o,l,h,m,b,v,y=a.fetchIfRef(e[0]).name;switch(y){case"DeviceGray":case"G":return"DeviceGrayCS";case"DeviceRGB":case"RGB":return"DeviceRgbCS";case"DeviceCMYK":case"CMYK":return"DeviceCmykCS";case"CalGray":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");v=l.get("Gamma");return["CalGrayCS",m,b,v];case"CalRGB":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");v=l.getArray("Gamma");var k=l.getArray("Matrix");return["CalRGBCS",m,b,v,k];case"ICCBased":var w=a.fetchIfRef(e[1]),C=w.dict;o=C.get("N");h=C.get("Alternate");if(h){var x=t.parseToIR(h,a,r),S=t.fromIR(x);if(S.numComps===o)return x;u("ICCBased color space: Ignoring incorrect /Alternate entry.")}if(1===o)return"DeviceGrayCS";if(3===o)return"DeviceRgbCS";if(4===o)return"DeviceCmykCS";break;case"Pattern":var A=e[1]||null;A&&(A=t.parseToIR(A,a,r));return["PatternCS",A];case"Indexed":case"I":var I=t.parseToIR(e[1],a,r),B=a.fetchIfRef(e[2])+1,R=a.fetchIfRef(e[3]);g(R)&&(R=R.getBytes());return["IndexedCS",I,B,R];case"Separation":case"DeviceN":var T=a.fetchIfRef(e[1]);o=c(T)?T.length:1;h=t.parseToIR(e[2],a,r);var O=p.getIR(a,a.fetchIfRef(e[3]));return["AlternateCS",o,h,O];case"Lab":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");var P=l.getArray("Range");return["LabCS",m,b,P];default:s('unimplemented color space object "'+y+'"')}}else s('unrecognized color space object: "'+e+'"');return null};t.isDefaultDecode=function(e,t){if(!c(e))return!0;if(2*t!==e.length){u("The decode map is not the correct length");return!0}for(var a=0,r=e.length;a<r;a+=2)if(0!==e[a]||1!==e[a+1])return!1;return!0};t.singletons={get gray(){return h(this,"gray",new k)},get rgb(){return h(this,"rgb",new w)},get cmyk(){return h(this,"cmyk",new C)}};return t}(),b=function(){function e(e,t,a){this.name="Alternate";this.numComps=e;this.defaultColor=new Float32Array(e);for(var r=0;r<e;++r)this.defaultColor[r]=1;this.base=t;this.tintFn=a;this.tmpBuf=new Float32Array(t.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=this.tmpBuf;this.tintFn(e,t,i,0);this.base.getRgbItem(i,0,a,r)},getRgbBuffer:function(e,t,a,r,i,n,s){var o,c,l=this.tintFn,h=this.base,u=1/((1<<n)-1),f=h.numComps,d=h.usesZeroToOneRange,g=(h.isPassthrough(8)||!d)&&0===s,p=g?i:0,m=g?r:new Uint8Array(f*a),b=this.numComps,v=new Float32Array(b),y=new Float32Array(f);for(o=0;o<a;o++){for(c=0;c<b;c++)v[c]=e[t++]*u;l(v,0,y,0);if(d)for(c=0;c<f;c++)m[p++]=255*y[c];else{h.getRgbItem(y,0,m,p);p+=f}}g||h.getRgbBuffer(m,0,a,r,i,8,s)},getOutputLength:function(e,t){return this.base.getOutputLength(e*this.base.numComps/this.numComps,t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),v=function(){function e(e){this.name="Pattern";this.base=e}e.prototype={};return e}(),y=function(){function e(e,t,a){this.name="Indexed";this.numComps=1;this.defaultColor=new Uint8Array(this.numComps);this.base=e;this.highVal=t;var r=e.numComps,i=r*t;if(g(a)){this.lookup=new Uint8Array(i);var n=a.getBytes(i);this.lookup.set(n)}else if(l(a)){this.lookup=new Uint8Array(i);for(var o=0;o<i;++o)this.lookup[o]=a.charCodeAt(o)}else a instanceof Uint8Array||a instanceof Array?this.lookup=a:s("Unrecognized lookup table: "+a)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=this.base.numComps,n=e[t]*i;this.base.getRgbItem(this.lookup,n,a,r)},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=this.base,c=o.numComps,l=o.getOutputLength(c,s),h=this.lookup,u=0;u<a;++u){var f=e[t++]*c;o.getRgbBuffer(h,f,1,r,i,8,s);i+=l}},getOutputLength:function(e,t){return this.base.getOutputLength(e*this.base.numComps,t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return!0},usesZeroToOneRange:!0};return e}(),k=function(){function e(){this.name="DeviceGray";this.numComps=1;this.defaultColor=new Float32Array(this.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=255*e[t]|0;i=i<0?0:i>255?255:i;a[r]=a[r+1]=a[r+2]=i},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=255/((1<<n)-1),c=t,l=i,h=0;h<a;++h){var u=o*e[c++]|0;r[l++]=u;r[l++]=u;r[l++]=u;l+=s}},getOutputLength:function(e,t){return e*(3+t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),w=function(){function e(){this.name="DeviceRGB";this.numComps=3;this.defaultColor=new Float32Array(this.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=255*e[t]|0,n=255*e[t+1]|0,s=255*e[t+2]|0;a[r]=i<0?0:i>255?255:i;a[r+1]=n<0?0:n>255?255:n;a[r+2]=s<0?0:s>255?255:s},getRgbBuffer:function(e,t,a,r,i,n,s){if(8!==n||0!==s)for(var o=255/((1<<n)-1),c=t,l=i,h=0;h<a;++h){r[l++]=o*e[c++]|0;r[l++]=o*e[c++]|0;r[l++]=o*e[c++]|0;l+=s}else r.set(e.subarray(t,t+3*a),i)},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:function(e){return 8===e},fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),C=function(){function e(e,t,a,r,i){var n=e[t+0]*a,s=e[t+1]*a,o=e[t+2]*a,c=e[t+3]*a,l=n*(-4.387332384609988*n+54.48615194189176*s+18.82290502165302*o+212.25662451639585*c-285.2331026137004)+s*(1.7149763477362134*s-5.6096736904047315*o+-17.873870861415444*c-5.497006427196366)+o*(-2.5217340131683033*o-21.248923337353073*c+17.5119270841813)+c*(-21.86122147463605*c-189.48180835922747)+255|0,h=n*(8.841041422036149*n+60.118027045597366*s+6.871425592049007*o+31.159100130055922*c-79.2970844816548)+s*(-15.310361306967817*s+17.575251261109482*o+131.35250912493976*c-190.9453302588951)+o*(4.444339102852739*o+9.8632861493405*c-24.86741582555878)+c*(-20.737325471181034*c-187.80453709719578)+255|0,u=n*(.8842522430003296*n+8.078677503112928*s+30.89978309703729*o-.23883238689178934*c-14.183576799673286)+s*(10.49593273432072*s+63.02378494754052*o+50.606957656360734*c-112.23884253719248)+o*(.03296041114873217*o+115.60384449646641*c-193.58209356861505)+c*(-22.33816807309886*c-180.12613974708367)+255|0;r[i]=l>255?255:l<0?0:l;r[i+1]=h>255?255:h<0?0:h;r[i+2]=u>255?255:u<0?0:u}function t(){this.name="DeviceCMYK";this.numComps=4;this.defaultColor=new Float32Array(this.numComps);this.defaultColor[3]=1}t.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(t,a,r,i){e(t,a,1,r,i)},getRgbBuffer:function(t,a,r,i,n,s,o){for(var c=1/((1<<s)-1),l=0;l<r;l++){e(t,a,c,i,n);a+=4;n+=3+o}},getOutputLength:function(e,t){return e/4*(3+t)|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return t}(),x=function(){function e(e,t,a){this.name="CalGray";this.numComps=1;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space CalGray");t=t||[0,0,0];a=a||1;this.XW=e[0];this.YW=e[1];this.ZW=e[2];this.XB=t[0];this.YB=t[1];this.ZB=t[2];this.G=a;(this.XW<0||this.ZW<0||1!==this.YW)&&s("Invalid WhitePoint components for "+this.name+", no fallback available");if(this.XB<0||this.YB<0||this.ZB<0){ +o("Invalid BlackPoint for "+this.name+", falling back to default");this.XB=this.YB=this.ZB=0}0===this.XB&&0===this.YB&&0===this.ZB||u(this.name+", BlackPoint: XB: "+this.XB+", YB: "+this.YB+", ZB: "+this.ZB+", only default values are supported.");if(this.G<1){o("Invalid Gamma: "+this.G+" for "+this.name+", falling back to default");this.G=1}}function t(e,t,a,r,i,n){var s=t[a]*n,o=Math.pow(s,e.G),c=e.YW*o,l=0|Math.max(295.8*Math.pow(c,.3333333333333333)-40.8,0);r[i]=l;r[i+1]=l;r[i+2]=l}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,a,r,i){t(this,e,a,r,i,1)},getRgbBuffer:function(e,a,r,i,n,s,o){for(var c=1/((1<<s)-1),l=0;l<r;++l){t(this,e,a,i,n,c);a+=1;n+=3+o}},getOutputLength:function(e,t){return e*(3+t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),S=function(){function e(e,t,a,r){this.name="CalRGB";this.numComps=3;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space CalRGB");t=t||new Float32Array(3);a=a||new Float32Array([1,1,1]);r=r||new Float32Array([1,0,0,0,1,0,0,0,1]);var i=e[0],n=e[1],c=e[2];this.whitePoint=e;var l=t[0],h=t[1],u=t[2];this.blackPoint=t;this.GR=a[0];this.GG=a[1];this.GB=a[2];this.MXA=r[0];this.MYA=r[1];this.MZA=r[2];this.MXB=r[3];this.MYB=r[4];this.MZB=r[5];this.MXC=r[6];this.MYC=r[7];this.MZC=r[8];(i<0||c<0||1!==n)&&s("Invalid WhitePoint components for "+this.name+", no fallback available");if(l<0||h<0||u<0){o("Invalid BlackPoint for "+this.name+" ["+l+", "+h+", "+u+"], falling back to default");this.blackPoint=new Float32Array(3)}if(this.GR<0||this.GG<0||this.GB<0){o("Invalid Gamma ["+this.GR+", "+this.GG+", "+this.GB+"] for "+this.name+", falling back to default");this.GR=this.GG=this.GB=1}if(this.MXA<0||this.MYA<0||this.MZA<0||this.MXB<0||this.MYB<0||this.MZB<0||this.MXC<0||this.MYC<0||this.MZC<0){o("Invalid Matrix for "+this.name+" ["+this.MXA+", "+this.MYA+", "+this.MZA+this.MXB+", "+this.MYB+", "+this.MZB+this.MXC+", "+this.MYC+", "+this.MZC+"], falling back to default");this.MXA=this.MYB=this.MZC=1;this.MXB=this.MYA=this.MZA=this.MXC=this.MYC=this.MZB=0}}function t(e,t,a){a[0]=e[0]*t[0]+e[1]*t[1]+e[2]*t[2];a[1]=e[3]*t[0]+e[4]*t[1]+e[5]*t[2];a[2]=e[6]*t[0]+e[7]*t[1]+e[8]*t[2]}function a(e,t,a){a[0]=1*t[0]/e[0];a[1]=1*t[1]/e[1];a[2]=1*t[2]/e[2]}function r(e,t,a){a[0]=.95047*t[0]/e[0];a[1]=1*t[1]/e[1];a[2]=1.08883*t[2]/e[2]}function i(e){return e<=.0031308?n(0,1,12.92*e):n(0,1,1.055*Math.pow(e,1/2.4)-.055)}function n(e,t,a){return Math.max(e,Math.min(t,a))}function c(e){return e<0?-c(-e):e>8?Math.pow((e+16)/116,3):e*w}function l(e,t,a){if(0!==e[0]||0!==e[1]||0!==e[2]){var r=c(0),i=r,n=c(e[0]),s=r,o=c(e[1]),l=r,h=c(e[2]),u=(1-i)/(1-n),f=1-u,d=(1-s)/(1-o),g=1-d,p=(1-l)/(1-h),m=1-p;a[0]=t[0]*u+f;a[1]=t[1]*d+g;a[2]=t[2]*p+m}else{a[0]=t[0];a[1]=t[1];a[2]=t[2]}}function h(e,r,i){if(1!==e[0]||1!==e[2]){var n=i;t(d,r,n);var s=v;a(e,n,s);t(g,s,i)}else{i[0]=r[0];i[1]=r[1];i[2]=r[2]}}function u(e,a,i){var n=i;t(d,a,n);var s=v;r(e,n,s);t(g,s,i)}function f(e,a,r,s,o,c){var f=n(0,1,a[r]*c),d=n(0,1,a[r+1]*c),g=n(0,1,a[r+2]*c),m=Math.pow(f,e.GR),v=Math.pow(d,e.GG),w=Math.pow(g,e.GB),C=e.MXA*m+e.MXB*v+e.MXC*w,x=e.MYA*m+e.MYB*v+e.MYC*w,S=e.MZA*m+e.MZB*v+e.MZC*w,A=y;A[0]=C;A[1]=x;A[2]=S;var I=k;h(e.whitePoint,A,I);var B=y;l(e.blackPoint,I,B);var R=k;u(b,B,R);var T=y;t(p,R,T);var O=i(T[0]),P=i(T[1]),M=i(T[2]);s[o]=Math.round(255*O);s[o+1]=Math.round(255*P);s[o+2]=Math.round(255*M)}var d=new Float32Array([.8951,.2664,-.1614,-.7502,1.7135,.0367,.0389,-.0685,1.0296]),g=new Float32Array([.9869929,-.1470543,.1599627,.4323053,.5183603,.0492912,-.0085287,.0400428,.9684867]),p=new Float32Array([3.2404542,-1.5371385,-.4985314,-.969266,1.8760108,.041556,.0556434,-.2040259,1.0572252]),b=new Float32Array([1,1,1]),v=new Float32Array(3),y=new Float32Array(3),k=new Float32Array(3),w=Math.pow(24/116,3)/8;e.prototype={getRgb:function(e,t){var a=new Uint8Array(3);this.getRgbItem(e,t,a,0);return a},getRgbItem:function(e,t,a,r){f(this,e,t,a,r,1)},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=1/((1<<n)-1),c=0;c<a;++c){f(this,e,t,r,i,o);t+=3;i+=3+s}},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),A=function(){function e(e,t,a){this.name="Lab";this.numComps=3;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space Lab");t=t||[0,0,0];a=a||[-100,100,-100,100];this.XW=e[0];this.YW=e[1];this.ZW=e[2];this.amin=a[0];this.amax=a[1];this.bmin=a[2];this.bmax=a[3];this.XB=t[0];this.YB=t[1];this.ZB=t[2];(this.XW<0||this.ZW<0||1!==this.YW)&&s("Invalid WhitePoint components, no fallback available");if(this.XB<0||this.YB<0||this.ZB<0){o("Invalid BlackPoint, falling back to default");this.XB=this.YB=this.ZB=0}if(this.amin>this.amax||this.bmin>this.bmax){o("Invalid Range, falling back to defaults");this.amin=-100;this.amax=100;this.bmin=-100;this.bmax=100}}function t(e){var t;t=e>=6/29?e*e*e:108/841*(e-4/29);return t}function a(e,t,a,r){return a+e*(r-a)/t}function r(e,r,i,n,s,o){var c=r[i],l=r[i+1],h=r[i+2];if(!1!==n){c=a(c,n,0,100);l=a(l,n,e.amin,e.amax);h=a(h,n,e.bmin,e.bmax)}l=l>e.amax?e.amax:l<e.amin?e.amin:l;h=h>e.bmax?e.bmax:h<e.bmin?e.bmin:h;var u,f,d,g=(c+16)/116,p=g+l/500,m=g-h/200,b=e.XW*t(p),v=e.YW*t(g),y=e.ZW*t(m);if(e.ZW<1){u=3.1339*b+-1.617*v+-.4906*y;f=-.9785*b+1.916*v+.0333*y;d=.072*b+-.229*v+1.4057*y}else{u=3.2406*b+-1.5372*v+-.4986*y;f=-.9689*b+1.8758*v+.0415*y;d=.0557*b+-.204*v+1.057*y}s[o]=u<=0?0:u>=1?255:255*Math.sqrt(u)|0;s[o+1]=f<=0?0:f>=1?255:255*Math.sqrt(f)|0;s[o+2]=d<=0?0:d>=1?255:255*Math.sqrt(d)|0}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,i){r(this,e,t,!1,a,i)},getRgbBuffer:function(e,t,a,i,n,s,o){for(var c=(1<<s)-1,l=0;l<a;l++){r(this,e,t,c,i,n);t+=3;n+=3+o}},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return!0},usesZeroToOneRange:!1};return e}();t.ColorSpace=m},function(e,t,a){"use strict";function r(e){switch(e){case"WinAnsiEncoding":return c;case"StandardEncoding":return o;case"MacRomanEncoding":return s;case"SymbolSetEncoding":return l;case"ZapfDingbatsEncoding":return h;case"ExpertEncoding":return i;case"MacExpertEncoding":return n;default:return null}}var i=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","","asuperior","bsuperior","centsuperior","dsuperior","esuperior","","","isuperior","","","lsuperior","msuperior","nsuperior","osuperior","","","rsuperior","ssuperior","tsuperior","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdownsmall","centoldstyle","Lslashsmall","","","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","","Dotaccentsmall","","","Macronsmall","","","figuredash","hypheninferior","","","Ogoneksmall","Ringsmall","Cedillasmall","","","","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],n=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","","threequartersemdash","","questionsmall","","","","","Ethsmall","","","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","","","","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hypheninferior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","asuperior","centsuperior","","","","","Aacutesmall","Agravesmall","Acircumflexsmall","Adieresissmall","Atildesmall","Aringsmall","Ccedillasmall","Eacutesmall","Egravesmall","Ecircumflexsmall","Edieresissmall","Iacutesmall","Igravesmall","Icircumflexsmall","Idieresissmall","Ntildesmall","Oacutesmall","Ogravesmall","Ocircumflexsmall","Odieresissmall","Otildesmall","Uacutesmall","Ugravesmall","Ucircumflexsmall","Udieresissmall","","eightsuperior","fourinferior","threeinferior","sixinferior","eightinferior","seveninferior","Scaronsmall","","centinferior","twoinferior","","Dieresissmall","","Caronsmall","osuperior","fiveinferior","","commainferior","periodinferior","Yacutesmall","","dollarinferior","","Thornsmall","","nineinferior","zeroinferior","Zcaronsmall","AEsmall","Oslashsmall","questiondownsmall","oneinferior","Lslashsmall","","","","","","","Cedillasmall","","","","","","OEsmall","figuredash","hyphensuperior","","","","","exclamdownsmall","","Ydieresissmall","","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","ninesuperior","zerosuperior","","esuperior","rsuperior","tsuperior","","","isuperior","ssuperior","dsuperior","","","","","","lsuperior","Ogoneksmall","Brevesmall","Macronsmall","bsuperior","nsuperior","msuperior","commasuperior","periodsuperior","Dotaccentsmall","Ringsmall"],s=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","space","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron"],o=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","","endash","dagger","daggerdbl","periodcentered","","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","","questiondown","","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","","ring","cedilla","","hungarumlaut","ogonek","caron","emdash","","","","","","","","","","","","","","","","","AE","","ordfeminine","","","","","Lslash","Oslash","OE","ordmasculine","","","","","","ae","","","","dotlessi","","","lslash","oslash","oe","germandbls"],c=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","bullet","Euro","bullet","quotesinglbase","florin","quotedblbase","ellipsis","dagger","daggerdbl","circumflex","perthousand","Scaron","guilsinglleft","OE","bullet","Zcaron","bullet","bullet","quoteleft","quoteright","quotedblleft","quotedblright","bullet","endash","emdash","tilde","trademark","scaron","guilsinglright","oe","bullet","zcaron","Ydieresis","space","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis","copyright","ordfeminine","guillemotleft","logicalnot","hyphen","registered","macron","degree","plusminus","twosuperior","threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright","onequarter","onehalf","threequarters","questiondown","Agrave","Aacute","Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute","Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth","Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash","Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn","germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae","ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex","idieresis","eth","ntilde","ograve","oacute","ocircumflex","otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex","udieresis","yacute","thorn","ydieresis"],l=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","universal","numbersign","existential","percent","ampersand","suchthat","parenleft","parenright","asteriskmath","plus","comma","minus","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","congruent","Alpha","Beta","Chi","Delta","Epsilon","Phi","Gamma","Eta","Iota","theta1","Kappa","Lambda","Mu","Nu","Omicron","Pi","Theta","Rho","Sigma","Tau","Upsilon","sigma1","Omega","Xi","Psi","Zeta","bracketleft","therefore","bracketright","perpendicular","underscore","radicalex","alpha","beta","chi","delta","epsilon","phi","gamma","eta","iota","phi1","kappa","lambda","mu","nu","omicron","pi","theta","rho","sigma","tau","upsilon","omega1","omega","xi","psi","zeta","braceleft","bar","braceright","similar","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Euro","Upsilon1","minute","lessequal","fraction","infinity","florin","club","diamond","heart","spade","arrowboth","arrowleft","arrowup","arrowright","arrowdown","degree","plusminus","second","greaterequal","multiply","proportional","partialdiff","bullet","divide","notequal","equivalence","approxequal","ellipsis","arrowvertex","arrowhorizex","carriagereturn","aleph","Ifraktur","Rfraktur","weierstrass","circlemultiply","circleplus","emptyset","intersection","union","propersuperset","reflexsuperset","notsubset","propersubset","reflexsubset","element","notelement","angle","gradient","registerserif","copyrightserif","trademarkserif","product","radical","dotmath","logicalnot","logicaland","logicalor","arrowdblboth","arrowdblleft","arrowdblup","arrowdblright","arrowdbldown","lozenge","angleleft","registersans","copyrightsans","trademarksans","summation","parenlefttp","parenleftex","parenleftbt","bracketlefttp","bracketleftex","bracketleftbt","bracelefttp","braceleftmid","braceleftbt","braceex","","angleright","integral","integraltp","integralex","integralbt","parenrighttp","parenrightex","parenrightbt","bracketrighttp","bracketrightex","bracketrightbt","bracerighttp","bracerightmid","bracerightbt"],h=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13","a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25","a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34","a35","a36","a37","a38","a39","a40","a41","a42","a43","a44","a45","a46","a47","a48","a49","a50","a51","a52","a53","a54","a55","a56","a57","a58","a59","a60","a61","a62","a63","a64","a65","a66","a67","a68","a69","a70","a71","a72","a73","a74","a203","a75","a204","a76","a77","a78","a79","a81","a82","a83","a84","a97","a98","a99","a100","","a89","a90","a93","a94","a91","a92","a205","a85","a206","a86","a87","a88","a95","a96","","","","","","","","","","","","","","","","","","","","a101","a102","a103","a104","a106","a107","a108","a112","a111","a110","a109","a120","a121","a122","a123","a124","a125","a126","a127","a128","a129","a130","a131","a132","a133","a134","a135","a136","a137","a138","a139","a140","a141","a142","a143","a144","a145","a146","a147","a148","a149","a150","a151","a152","a153","a154","a155","a156","a157","a158","a159","a160","a161","a163","a164","a196","a165","a192","a166","a167","a168","a169","a170","a171","a172","a173","a162","a174","a175","a176","a177","a178","a179","a193","a180","a199","a181","a200","a182","","a201","a183","a184","a197","a185","a194","a198","a186","a195","a187","a188","a189","a190","a191"];t.WinAnsiEncoding=c;t.StandardEncoding=o;t.MacRomanEncoding=s;t.SymbolSetEncoding=l;t.ZapfDingbatsEncoding=h;t.ExpertEncoding=i;t.getEncoding=r},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=r.MissingDataException,o=r.StreamType,c=r.assert,l=r.error,h=r.info,u=r.isArray,f=r.isInt,d=r.isNum,g=r.isString,p=r.warn,m=i.EOF,b=i.Cmd,v=i.Dict,y=i.Name,k=i.Ref,w=i.isEOF,C=i.isCmd,x=i.isDict,S=i.isName,A=n.Ascii85Stream,I=n.AsciiHexStream,B=n.CCITTFaxStream,R=n.FlateStream,T=n.Jbig2Stream,O=n.JpegStream,P=n.JpxStream,M=n.LZWStream,E=n.NullStream,L=n.PredictorStream,D=n.RunLengthStream,F=function(){function e(e,t,a,r){this.lexer=e;this.allowStreams=t;this.xref=a;this.recoveryMode=r||!1;this.imageCache=Object.create(null);this.refill()}e.prototype={refill:function(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()},shift:function(){if(C(this.buf2,"ID")){this.buf1=this.buf2;this.buf2=null}else{this.buf1=this.buf2;this.buf2=this.lexer.getObj()}},tryShift:function(){try{this.shift();return!0}catch(e){if(e instanceof s)throw e;return!1}},getObj:function(e){var t=this.buf1;this.shift();if(t instanceof b)switch(t.cmd){case"BI":return this.makeInlineImage(e);case"[":for(var a=[];!C(this.buf1,"]")&&!w(this.buf1);)a.push(this.getObj(e));if(w(this.buf1)){this.recoveryMode||l("End of file inside array");return a}this.shift();return a;case"<<":for(var r=new v(this.xref);!C(this.buf1,">>")&&!w(this.buf1);)if(S(this.buf1)){var i=this.buf1.name;this.shift();if(w(this.buf1))break;r.set(i,this.getObj(e))}else{h("Malformed dictionary: key must be a name object");this.shift()}if(w(this.buf1)){this.recoveryMode||l("End of file inside dictionary");return r}if(C(this.buf2,"stream"))return this.allowStreams?this.makeStream(r,e):r;this.shift();return r;default:return t}if(f(t)){var n=t;if(f(this.buf1)&&C(this.buf2,"R")){var s=new k(n,this.buf1);this.shift();this.shift();return s}return n}if(g(t)){var o=t;e&&(o=e.decryptString(o));return o}return t},findDefaultInlineStreamEnd:function(e){for(var t,a,r,i,n=e.pos,s=0;-1!==(t=e.getByte());)if(0===s)s=69===t?1:0;else if(1===s)s=73===t?2:0;else{c(2===s);if(32===t||10===t||13===t){r=5;i=e.peekBytes(r);for(a=0;a<r;a++){t=i[a];if(10!==t&&13!==t&&(t<32||t>127)){s=0;break}}if(2===s)break}else s=0}return e.pos-4-n},findDCTDecodeInlineStreamEnd:function(e){for(var t,a,r,i=e.pos,n=!1;-1!==(t=e.getByte());)if(255===t){switch(e.getByte()){case 0:break;case 255:e.skip(-1);break;case 217:n=!0;break;case 192:case 193:case 194:case 195:case 197:case 198:case 199:case 201:case 202:case 203:case 205:case 206:case 207:case 196:case 204:case 218:case 219:case 220:case 221:case 222:case 223:case 224:case 225:case 226:case 227:case 228:case 229:case 230:case 231:case 232:case 233:case 234:case 235:case 236:case 237:case 238:case 239:case 254:a=e.getUint16();a>2?e.skip(a-2):e.skip(-2)}if(n)break}r=e.pos-i;if(-1===t){p("Inline DCTDecode image stream: EOI marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r},findASCII85DecodeInlineStreamEnd:function(e){for(var t,a,r=e.pos;-1!==(t=e.getByte());)if(126===t&&62===e.peekByte()){e.skip();break}a=e.pos-r;if(-1===t){p("Inline ASCII85Decode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-a);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return a},findASCIIHexDecodeInlineStreamEnd:function(e){for(var t,a,r=e.pos;-1!==(t=e.getByte())&&62!==t;);a=e.pos-r;if(-1===t){p("Inline ASCIIHexDecode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-a);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return a},inlineStreamSkipEI:function(e){for(var t,a=0;-1!==(t=e.getByte());)if(0===a)a=69===t?1:0;else if(1===a)a=73===t?2:0;else if(2===a)break},makeInlineImage:function(e){for(var t=this.lexer,a=t.stream,r=new v(this.xref);!C(this.buf1,"ID")&&!w(this.buf1);){S(this.buf1)||l("Dictionary key must be a name object");var i=this.buf1.name;this.shift();if(w(this.buf1))break;r.set(i,this.getObj(e))}var n,s=r.get("Filter","F");if(S(s))n=s.name;else if(u(s)){var o=this.xref.fetchIfRef(s[0]);S(o)&&(n=o.name)}var c,h,f,d=a.pos;c="DCTDecode"===n||"DCT"===n?this.findDCTDecodeInlineStreamEnd(a):"ASCII85Decide"===n||"A85"===n?this.findASCII85DecodeInlineStreamEnd(a):"ASCIIHexDecode"===n||"AHx"===n?this.findASCIIHexDecodeInlineStreamEnd(a):this.findDefaultInlineStreamEnd(a);var g,p=a.makeSubStream(d,c,r);if(c<1e3){var m=p.getBytes();p.reset();var y=1,k=0;for(h=0,f=m.length;h<f;++h){y+=255&m[h];k+=y}g=k%65521<<16|y%65521;if(this.imageCache.adler32===g){this.buf2=b.get("EI");this.shift();this.imageCache[g].reset();return this.imageCache[g]}}e&&(p=e.createStream(p,c));p=this.filter(p,r,c);p.dict=r;if(void 0!==g){p.cacheKey="inline_"+c+"_"+g;this.imageCache[g]=p}this.buf2=b.get("EI");this.shift();return p},makeStream:function(e,t){var a=this.lexer,r=a.stream;a.skipToNextLine();var i=r.pos-1,n=e.get("Length");if(!f(n)){h("Bad "+n+" attribute in stream");n=0}r.pos=i+n;a.nextChar();if(this.tryShift()&&C(this.buf2,"endstream"))this.shift();else{r.pos=i;for(var s,o,c=[101,110,100,115,116,114,101,97,109],u=0,d=!1;r.pos<r.end;){var g=r.peekBytes(2048),p=g.length-9;if(p<=0)break;d=!1;s=0;for(;s<p;){o=0;for(;o<9&&g[s+o]===c[o];)o++;if(o>=9){d=!0;break}s++}if(d){u+=s;r.pos+=s;break}u+=p;r.pos+=p}d||l("Missing endstream");n=u;a.nextChar();this.shift();this.shift()}this.shift();r=r.makeSubStream(i,n,e);t&&(r=t.createStream(r,n));r=this.filter(r,e,n);r.dict=e;return r},filter:function(e,t,a){var r=t.get("Filter","F"),i=t.get("DecodeParms","DP");if(S(r)){u(i)&&(i=this.xref.fetchIfRef(i[0]));return this.makeFilter(e,r.name,a,i)}var n=a;if(u(r))for(var s=r,o=i,c=0,h=s.length;c<h;++c){r=this.xref.fetchIfRef(s[c]);S(r)||l("Bad filter name: "+r);i=null;u(o)&&c in o&&(i=this.xref.fetchIfRef(o[c]));e=this.makeFilter(e,r.name,n,i);n=null}return e},makeFilter:function(e,t,a,r){if(0===a){p('Empty "'+t+'" stream.');return new E(e)}try{var i=this.xref.stats.streamTypes;if("FlateDecode"===t||"Fl"===t){i[o.FLATE]=!0;return r?new L(new R(e,a),a,r):new R(e,a)}if("LZWDecode"===t||"LZW"===t){i[o.LZW]=!0;var n=1;if(r){r.has("EarlyChange")&&(n=r.get("EarlyChange"));return new L(new M(e,a,n),a,r)}return new M(e,a,n)}if("DCTDecode"===t||"DCT"===t){i[o.DCT]=!0;return new O(e,a,e.dict,r)}if("JPXDecode"===t||"JPX"===t){i[o.JPX]=!0;return new P(e,a,e.dict,r)}if("ASCII85Decode"===t||"A85"===t){i[o.A85]=!0;return new A(e,a)}if("ASCIIHexDecode"===t||"AHx"===t){i[o.AHX]=!0;return new I(e,a)}if("CCITTFaxDecode"===t||"CCF"===t){i[o.CCF]=!0;return new B(e,a,r)}if("RunLengthDecode"===t||"RL"===t){i[o.RL]=!0;return new D(e,a)}if("JBIG2Decode"===t){i[o.JBIG]=!0;return new T(e,a,e.dict,r)}p('filter "'+t+'" not supported yet');return e}catch(t){if(t instanceof s)throw t;p('Invalid stream: "'+t+'"');return new E(e)}}};return e}(),q=function(){function e(e,t){this.stream=e;this.nextChar();this.strBuf=[];this.knownCommands=t}function t(e){return e>=48&&e<=57?15&e:e>=65&&e<=70||e>=97&&e<=102?9+(15&e):-1}var a=[1,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];e.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},peekChar:function(){return this.stream.peekByte()},getNumber:function(){var e=this.currentChar,t=!1,a=0,r=1;if(45===e){r=-1;e=this.nextChar();45===e&&(e=this.nextChar())}else 43===e&&(e=this.nextChar());if(46===e){a=10;e=this.nextChar()}if(e<48||e>57){l("Invalid number: "+String.fromCharCode(e));return 0}for(var i=e-48,n=0,s=1;(e=this.nextChar())>=0;)if(48<=e&&e<=57){var o=e-48;if(t)n=10*n+o;else{0!==a&&(a*=10);i=10*i+o}}else if(46===e){if(0!==a)break;a=1}else if(45===e)p("Badly formatted number");else{if(69!==e&&101!==e)break;e=this.peekChar();if(43===e||45===e){s=45===e?-1:1;this.nextChar()}else if(e<48||e>57)break;t=!0}0!==a&&(i/=a);t&&(i*=Math.pow(10,s*n));return r*i},getString:function(){var e=1,t=!1,a=this.strBuf;a.length=0;for(var r=this.nextChar();;){var i=!1;switch(0|r){case-1:p("Unterminated string");t=!0;break;case 40:++e;a.push("(");break;case 41:if(0==--e){this.nextChar();t=!0}else a.push(")");break;case 92:r=this.nextChar();switch(r){case-1:p("Unterminated string");t=!0;break;case 110:a.push("\n");break;case 114:a.push("\r");break;case 116:a.push("\t");break;case 98:a.push("\b");break;case 102:a.push("\f");break;case 92:case 40:case 41:a.push(String.fromCharCode(r));break;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:var n=15&r;r=this.nextChar();i=!0;if(r>=48&&r<=55){n=(n<<3)+(15&r);r=this.nextChar();if(r>=48&&r<=55){i=!1;n=(n<<3)+(15&r)}}a.push(String.fromCharCode(n));break;case 13:10===this.peekChar()&&this.nextChar();break;case 10:break;default:a.push(String.fromCharCode(r))}break;default:a.push(String.fromCharCode(r))}if(t)break;i||(r=this.nextChar())}return a.join("")},getName:function(){var e,r,i=this.strBuf;i.length=0;for(;(e=this.nextChar())>=0&&!a[e];)if(35===e){e=this.nextChar();if(a[e]){p("Lexer_getName: NUMBER SIGN (#) should be followed by a hexadecimal number.");i.push("#");break}var n=t(e);if(-1!==n){r=e;e=this.nextChar();var s=t(e);if(-1===s){p("Lexer_getName: Illegal digit ("+String.fromCharCode(e)+") in hexadecimal number.");i.push("#",String.fromCharCode(r));if(a[e])break;i.push(String.fromCharCode(e));continue}i.push(String.fromCharCode(n<<4|s))}else i.push("#",String.fromCharCode(e))}else i.push(String.fromCharCode(e));i.length>127&&p("name token is longer than allowed by the spec: "+i.length);return y.get(i.join(""))},getHexString:function(){var e=this.strBuf;e.length=0;for(var r,i,n=this.currentChar,s=!0;;){if(n<0){p("Unterminated hex string");break}if(62===n){this.nextChar();break}if(1!==a[n]){if(s){r=t(n);if(-1===r){p('Ignoring invalid character "'+n+'" in hex string');n=this.nextChar();continue}}else{i=t(n);if(-1===i){p('Ignoring invalid character "'+n+'" in hex string');n=this.nextChar();continue}e.push(String.fromCharCode(r<<4|i))}s=!s;n=this.nextChar()}else n=this.nextChar()}return e.join("")},getObj:function(){for(var e=!1,t=this.currentChar;;){if(t<0)return m;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(1!==a[t])break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return this.getNumber();case 40:return this.getString();case 47:return this.getName();case 91:this.nextChar();return b.get("[");case 93:this.nextChar();return b.get("]");case 60:t=this.nextChar();if(60===t){this.nextChar();return b.get("<<")}return this.getHexString();case 62:t=this.nextChar();if(62===t){this.nextChar();return b.get(">>")} +return b.get(">");case 123:this.nextChar();return b.get("{");case 125:this.nextChar();return b.get("}");case 41:this.nextChar();l("Illegal character: "+t)}for(var r=String.fromCharCode(t),i=this.knownCommands,n=i&&void 0!==i[r];(t=this.nextChar())>=0&&!a[t];){var s=r+String.fromCharCode(t);if(n&&void 0===i[s])break;128===r.length&&l("Command token too long: "+r.length);r=s;n=i&&void 0!==i[r]}return"true"===r||"false"!==r&&("null"===r?null:b.get(r))},skipToNextLine:function(){for(var e=this.currentChar;e>=0;){if(13===e){e=this.nextChar();10===e&&this.nextChar();break}if(10===e){this.nextChar();break}e=this.nextChar()}}};return e}(),U={create:function(e){function t(e,t){var a=c.get(e);if(f(a)&&(t?a>=0:a>0))return a;throw new Error('The "'+e+'" parameter in the linearization dictionary is invalid.')}var a,r,i=new F(new q(e),!1,null),n=i.getObj(),s=i.getObj(),o=i.getObj(),c=i.getObj();if(!(f(n)&&f(s)&&C(o,"obj")&&x(c)&&d(a=c.get("Linearized"))&&a>0))return null;if((r=t("L"))!==e.length)throw new Error('The "L" parameter in the linearization dictionary does not equal the stream length.');return{length:r,hints:function(){var e,t,a=c.get("H");if(u(a)&&(2===(e=a.length)||4===e)){for(var r=0;r<e;r++)if(!(f(t=a[r])&&t>0))throw new Error("Hint ("+r+") in the linearization dictionary is invalid.");return a}throw new Error("Hint array in the linearization dictionary is invalid.")}(),objectNumberFirst:t("O"),endFirst:t("E"),numPages:t("N"),mainXRefEntriesOffset:t("T"),pageFirst:c.has("P")?t("P",!0):0}}};t.Lexer=q;t.Linearization=U;t.Parser=F},function(e,t,a){"use strict";function r(e){var t;if("object"!=typeof e)return!1;if(u(e))t=e;else{if(!f(e))return!1;t=e.dict}return t.has("FunctionType")}var i=a(0),n=a(1),s=a(34),o=i.error,c=i.info,l=i.isArray,h=i.isBool,u=n.isDict,f=n.isStream,d=s.PostScriptLexer,g=s.PostScriptParser,p=function(){return{getSampleArray:function(e,t,a,r){var i,n,s=1;for(i=0,n=e.length;i<n;i++)s*=e[i];s*=t;var o=new Array(s),c=0,l=0,h=1/(Math.pow(2,a)-1),u=r.getBytes((s*a+7)/8),f=0;for(i=0;i<s;i++){for(;c<a;){l<<=8;l|=u[f++];c+=8}c-=a;o[i]=(l>>c)*h;l&=(1<<c)-1}return o},getIR:function(e,t){var a=t.dict;a||(a=t);var r=[this.constructSampled,null,this.constructInterpolated,this.constructStiched,this.constructPostScript],i=a.get("FunctionType"),n=r[i];n||o("Unknown type of function");return n.call(this,t,a,e)},fromIR:function(e){switch(e[0]){case 0:return this.constructSampledFromIR(e);case 2:return this.constructInterpolatedFromIR(e);case 3:return this.constructStichedFromIR(e);default:return this.constructPostScriptFromIR(e)}},parse:function(e,t){var a=this.getIR(e,t);return this.fromIR(a)},parseArray:function(e,t){if(!l(t))return this.parse(e,t);for(var a=[],r=0,i=t.length;r<i;r++){var n=e.fetchIfRef(t[r]);a.push(p.parse(e,n))}return function(e,t,r,i){for(var n=0,s=a.length;n<s;n++)a[n](e,t,r,i+n)}},constructSampled:function(e,t){function a(e){for(var t=e.length,a=[],r=0,i=0;i<t;i+=2){a[r]=[e[i],e[i+1]];++r}return a}var r=t.getArray("Domain"),i=t.getArray("Range");r&&i||o("No domain or range");var n=r.length/2,s=i.length/2;r=a(r);i=a(i);var l=t.get("Size"),h=t.get("BitsPerSample"),u=t.get("Order")||1;1!==u&&c("No support for cubic spline interpolation: "+u);var f=t.getArray("Encode");if(!f){f=[];for(var d=0;d<n;++d){f.push(0);f.push(l[d]-1)}}f=a(f);var g=t.getArray("Decode");g=g?a(g):i;return[0,n,r,f,g,this.getSampleArray(l,s,h,e),l,s,Math.pow(2,h)-1,i]},constructSampledFromIR:function(e){function t(e,t,a,r,i){return r+(i-r)/(a-t)*(e-t)}return function(a,r,i,n){var s,o,c=e[1],l=e[2],h=e[3],u=e[4],f=e[5],d=e[6],g=e[7],p=e[9],m=1<<c,b=new Float64Array(m),v=new Uint32Array(m);for(o=0;o<m;o++)b[o]=1;var y=g,k=1;for(s=0;s<c;++s){var w=l[s][0],C=l[s][1],x=Math.min(Math.max(a[r+s],w),C),S=t(x,w,C,h[s][0],h[s][1]),A=d[s];S=Math.min(Math.max(S,0),A-1);var I=S<A-1?Math.floor(S):S-1,B=I+1-S,R=S-I,T=I*y,O=T+y;for(o=0;o<m;o++)if(o&k){b[o]*=R;v[o]+=O}else{b[o]*=B;v[o]+=T}y*=A;k<<=1}for(o=0;o<g;++o){var P=0;for(s=0;s<m;s++)P+=f[v[s]+o]*b[s];P=t(P,0,1,u[o][0],u[o][1]);i[n+o]=Math.min(Math.max(P,p[o][0]),p[o][1])}}},constructInterpolated:function(e,t){var a=t.getArray("C0")||[0],r=t.getArray("C1")||[1],i=t.get("N");l(a)&&l(r)||o("Illegal dictionary for interpolated function");for(var n=a.length,s=[],c=0;c<n;++c)s.push(r[c]-a[c]);return[2,a,s,i]},constructInterpolatedFromIR:function(e){var t=e[1],a=e[2],r=e[3],i=a.length;return function(e,n,s,o){for(var c=1===r?e[n]:Math.pow(e[n],r),l=0;l<i;++l)s[o+l]=t[l]+c*a[l]}},constructStiched:function(e,t,a){var r=t.getArray("Domain");r||o("No domain");1!=r.length/2&&o("Bad domain for stiched function");for(var i=t.get("Functions"),n=[],s=0,c=i.length;s<c;++s)n.push(p.getIR(a,a.fetchIfRef(i[s])));return[3,r,t.getArray("Bounds"),t.getArray("Encode"),n]},constructStichedFromIR:function(e){for(var t=e[1],a=e[2],r=e[3],i=e[4],n=[],s=new Float32Array(1),o=0,c=i.length;o<c;o++)n.push(p.fromIR(i[o]));return function(e,i,o,c){for(var l=function(e,t,a){e>a?e=a:e<t&&(e=t);return e}(e[i],t[0],t[1]),h=0,u=a.length;h<u&&!(l<a[h]);++h);var f=t[0];h>0&&(f=a[h-1]);var d=t[1];h<a.length&&(d=a[h]);var g=r[2*h],p=r[2*h+1];s[0]=f===d?g:g+(l-f)*(p-g)/(d-f);n[h](s,0,o,c)}},constructPostScript:function(e,t,a){var r=t.getArray("Domain"),i=t.getArray("Range");r||o("No domain.");i||o("No range.");var n=new d(e);return[4,r,i,new g(n).parse()]},constructPostScriptFromIR:function(e){var t=e[1],a=e[2],r=e[3],i=(new v).compile(r,t,a);if(i)return new Function("src","srcOffset","dest","destOffset",i);c("Unable to compile PS function");var n=a.length>>1,s=t.length>>1,o=new b(r),l=Object.create(null),h=8192,u=new Float32Array(s);return function(e,t,r,i){var c,f,d="",g=u;for(c=0;c<s;c++){f=e[t+c];g[c]=f;d+=f+"_"}var p=l[d];if(void 0===p){var m=new Float32Array(n),b=o.execute(g),v=b.length-n;for(c=0;c<n;c++){f=b[v+c];var y=a[2*c];if(f<y)f=y;else{y=a[2*c+1];f>y&&(f=y)}m[c]=f}if(h>0){h--;l[d]=m}r.set(m,i)}else r.set(p,i)}}}}(),m=function(){function e(e){this.stack=e?Array.prototype.slice.call(e,0):[]}e.prototype={push:function(e){this.stack.length>=100&&o("PostScript function stack overflow.");this.stack.push(e)},pop:function(){this.stack.length<=0&&o("PostScript function stack underflow.");return this.stack.pop()},copy:function(e){this.stack.length+e>=100&&o("PostScript function stack overflow.");for(var t=this.stack,a=t.length-e,r=e-1;r>=0;r--,a++)t.push(t[a])},index:function(e){this.push(this.stack[this.stack.length-e-1])},roll:function(e,t){var a,r,i,n=this.stack,s=n.length-e,o=n.length-1,c=s+(t-Math.floor(t/e)*e);for(a=s,r=o;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}for(a=s,r=c-1;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}for(a=c,r=o;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}}};return e}(),b=function(){function e(e){this.operators=e}e.prototype={execute:function(e){for(var t,a,r,i=new m(e),n=0,s=this.operators,c=s.length;n<c;){t=s[n++];if("number"!=typeof t)switch(t){case"jz":r=i.pop();a=i.pop();a||(n=r);break;case"j":a=i.pop();n=a;break;case"abs":a=i.pop();i.push(Math.abs(a));break;case"add":r=i.pop();a=i.pop();i.push(a+r);break;case"and":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a&&r):i.push(a&r);break;case"atan":a=i.pop();i.push(Math.atan(a));break;case"bitshift":r=i.pop();a=i.pop();a>0?i.push(a<<r):i.push(a>>r);break;case"ceiling":a=i.pop();i.push(Math.ceil(a));break;case"copy":a=i.pop();i.copy(a);break;case"cos":a=i.pop();i.push(Math.cos(a));break;case"cvi":a=0|i.pop();i.push(a);break;case"cvr":break;case"div":r=i.pop();a=i.pop();i.push(a/r);break;case"dup":i.copy(1);break;case"eq":r=i.pop();a=i.pop();i.push(a===r);break;case"exch":i.roll(2,1);break;case"exp":r=i.pop();a=i.pop();i.push(Math.pow(a,r));break;case"false":i.push(!1);break;case"floor":a=i.pop();i.push(Math.floor(a));break;case"ge":r=i.pop();a=i.pop();i.push(a>=r);break;case"gt":r=i.pop();a=i.pop();i.push(a>r);break;case"idiv":r=i.pop();a=i.pop();i.push(a/r|0);break;case"index":a=i.pop();i.index(a);break;case"le":r=i.pop();a=i.pop();i.push(a<=r);break;case"ln":a=i.pop();i.push(Math.log(a));break;case"log":a=i.pop();i.push(Math.log(a)/Math.LN10);break;case"lt":r=i.pop();a=i.pop();i.push(a<r);break;case"mod":r=i.pop();a=i.pop();i.push(a%r);break;case"mul":r=i.pop();a=i.pop();i.push(a*r);break;case"ne":r=i.pop();a=i.pop();i.push(a!==r);break;case"neg":a=i.pop();i.push(-a);break;case"not":a=i.pop();h(a)?i.push(!a):i.push(~a);break;case"or":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a||r):i.push(a|r);break;case"pop":i.pop();break;case"roll":r=i.pop();a=i.pop();i.roll(a,r);break;case"round":a=i.pop();i.push(Math.round(a));break;case"sin":a=i.pop();i.push(Math.sin(a));break;case"sqrt":a=i.pop();i.push(Math.sqrt(a));break;case"sub":r=i.pop();a=i.pop();i.push(a-r);break;case"true":i.push(!0);break;case"truncate":a=i.pop();a=a<0?Math.ceil(a):Math.floor(a);i.push(a);break;case"xor":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a!==r):i.push(a^r);break;default:o("Unknown operator "+t)}else i.push(t)}return i.stack}};return e}(),v=function(){function e(e){this.type=e}function t(t,a,r){e.call(this,"args");this.index=t;this.min=a;this.max=r}function a(t){e.call(this,"literal");this.number=t;this.min=t;this.max=t}function r(t,a,r,i,n){e.call(this,"binary");this.op=t;this.arg1=a;this.arg2=r;this.min=i;this.max=n}function i(t,a){e.call(this,"max");this.arg=t;this.min=t.min;this.max=a}function n(t,a,r){e.call(this,"var");this.index=t;this.min=a;this.max=r}function s(t,a){e.call(this,"definition");this.variable=t;this.arg=a}function o(){this.parts=[]}function c(e,t){return"literal"===t.type&&0===t.number?e:"literal"===e.type&&0===e.number?t:"literal"===t.type&&"literal"===e.type?new a(e.number+t.number):new r("+",e,t,e.min+t.min,e.max+t.max)}function l(e,t){if("literal"===t.type){if(0===t.number)return new a(0);if(1===t.number)return e;if("literal"===e.type)return new a(e.number*t.number)}if("literal"===e.type){if(0===e.number)return new a(0);if(1===e.number)return t}return new r("*",e,t,Math.min(e.min*t.min,e.min*t.max,e.max*t.min,e.max*t.max),Math.max(e.min*t.min,e.min*t.max,e.max*t.min,e.max*t.max))}function h(e,t){if("literal"===t.type){if(0===t.number)return e;if("literal"===e.type)return new a(e.number-t.number)}return"binary"===t.type&&"-"===t.op&&"literal"===e.type&&1===e.number&&"literal"===t.arg1.type&&1===t.arg1.number?t.arg2:new r("-",e,t,e.min-t.max,e.max-t.min)}function u(e,t){return e.min>=t?new a(t):e.max<=t?e:new i(e,t)}function f(){}e.prototype.visit=function(e){throw new Error("abstract method")};t.prototype=Object.create(e.prototype);t.prototype.visit=function(e){e.visitArgument(this)};a.prototype=Object.create(e.prototype);a.prototype.visit=function(e){e.visitLiteral(this)};r.prototype=Object.create(e.prototype);r.prototype.visit=function(e){e.visitBinaryOperation(this)};i.prototype=Object.create(e.prototype);i.prototype.visit=function(e){e.visitMin(this)};n.prototype=Object.create(e.prototype);n.prototype.visit=function(e){e.visitVariable(this)};s.prototype=Object.create(e.prototype);s.prototype.visit=function(e){e.visitVariableDefinition(this)};o.prototype={visitArgument:function(e){this.parts.push("Math.max(",e.min,", Math.min(",e.max,", src[srcOffset + ",e.index,"]))")},visitVariable:function(e){this.parts.push("v",e.index)},visitLiteral:function(e){this.parts.push(e.number)},visitBinaryOperation:function(e){this.parts.push("(");e.arg1.visit(this);this.parts.push(" ",e.op," ");e.arg2.visit(this);this.parts.push(")")},visitVariableDefinition:function(e){this.parts.push("var ");e.variable.visit(this);this.parts.push(" = ");e.arg.visit(this);this.parts.push(";")},visitMin:function(e){this.parts.push("Math.min(");e.arg.visit(this);this.parts.push(", ",e.max,")")},toString:function(){return this.parts.join("")}};f.prototype={compile:function(e,r,i){var f,d,g,p,m,b,v,y,k,w,C=[],x=[],S=r.length>>1,A=i.length>>1,I=0;for(f=0;f<S;f++)C.push(new t(f,r[2*f],r[2*f+1]));for(f=0,d=e.length;f<d;f++){w=e[f];if("number"!=typeof w)switch(w){case"add":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(c(m,b));break;case"cvr":if(C.length<1)return null;break;case"mul":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(l(m,b));break;case"sub":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(h(m,b));break;case"exch":if(C.length<2)return null;v=C.pop();y=C.pop();C.push(v,y);break;case"pop":if(C.length<1)return null;C.pop();break;case"index":if(C.length<1)return null;m=C.pop();if("literal"!==m.type)return null;g=m.number;if(g<0||(0|g)!==g||C.length<g)return null;v=C[C.length-g-1];if("literal"===v.type||"var"===v.type){C.push(v);break}k=new n(I++,v.min,v.max);C[C.length-g-1]=k;C.push(k);x.push(new s(k,v));break;case"dup":if(C.length<1)return null;if("number"==typeof e[f+1]&&"gt"===e[f+2]&&e[f+3]===f+7&&"jz"===e[f+4]&&"pop"===e[f+5]&&e[f+6]===e[f+1]){m=C.pop();C.push(u(m,e[f+1]));f+=6;break}v=C[C.length-1];if("literal"===v.type||"var"===v.type){C.push(v);break}k=new n(I++,v.min,v.max);C[C.length-1]=k;C.push(k);x.push(new s(k,v));break;case"roll":if(C.length<2)return null;b=C.pop();m=C.pop();if("literal"!==b.type||"literal"!==m.type)return null;p=b.number;g=m.number;if(g<=0||(0|g)!==g||(0|p)!==p||C.length<g)return null;p=(p%g+g)%g;if(0===p)break;Array.prototype.push.apply(C,C.splice(C.length-g,g-p));break;default:return null}else C.push(new a(w))}if(C.length!==A)return null;var B=[];x.forEach(function(e){var t=new o;e.visit(t);B.push(t.toString())});C.forEach(function(e,t){var a=new o;e.visit(a);var r=i[2*t],n=i[2*t+1],s=[a.toString()];if(r>e.min){s.unshift("Math.max(",r,", ");s.push(")")}if(n<e.max){s.unshift("Math.min(",n,", ");s.push(")")}s.unshift("dest[destOffset + ",t,"] = ");s.push(";");B.push(s.join(""))});return B.join("\n")}};return f}();t.isPDFFunction=r;t.PDFFunction=p;t.PostScriptEvaluator=b;t.PostScriptCompiler=v},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.A=65;e.AE=198;e.AEacute=508;e.AEmacron=482;e.AEsmall=63462;e.Aacute=193;e.Aacutesmall=63457;e.Abreve=258;e.Abreveacute=7854;e.Abrevecyrillic=1232;e.Abrevedotbelow=7862;e.Abrevegrave=7856;e.Abrevehookabove=7858;e.Abrevetilde=7860;e.Acaron=461;e.Acircle=9398;e.Acircumflex=194;e.Acircumflexacute=7844;e.Acircumflexdotbelow=7852;e.Acircumflexgrave=7846;e.Acircumflexhookabove=7848;e.Acircumflexsmall=63458;e.Acircumflextilde=7850;e.Acute=63177;e.Acutesmall=63412;e.Acyrillic=1040;e.Adblgrave=512;e.Adieresis=196;e.Adieresiscyrillic=1234;e.Adieresismacron=478;e.Adieresissmall=63460;e.Adotbelow=7840;e.Adotmacron=480;e.Agrave=192;e.Agravesmall=63456;e.Ahookabove=7842;e.Aiecyrillic=1236;e.Ainvertedbreve=514;e.Alpha=913;e.Alphatonos=902;e.Amacron=256;e.Amonospace=65313;e.Aogonek=260;e.Aring=197;e.Aringacute=506;e.Aringbelow=7680;e.Aringsmall=63461;e.Asmall=63329;e.Atilde=195;e.Atildesmall=63459;e.Aybarmenian=1329;e.B=66;e.Bcircle=9399;e.Bdotaccent=7682;e.Bdotbelow=7684;e.Becyrillic=1041;e.Benarmenian=1330;e.Beta=914;e.Bhook=385;e.Blinebelow=7686;e.Bmonospace=65314;e.Brevesmall=63220;e.Bsmall=63330;e.Btopbar=386;e.C=67;e.Caarmenian=1342;e.Cacute=262;e.Caron=63178;e.Caronsmall=63221;e.Ccaron=268;e.Ccedilla=199;e.Ccedillaacute=7688;e.Ccedillasmall=63463;e.Ccircle=9400;e.Ccircumflex=264;e.Cdot=266;e.Cdotaccent=266;e.Cedillasmall=63416;e.Chaarmenian=1353;e.Cheabkhasiancyrillic=1212;e.Checyrillic=1063;e.Chedescenderabkhasiancyrillic=1214;e.Chedescendercyrillic=1206;e.Chedieresiscyrillic=1268;e.Cheharmenian=1347;e.Chekhakassiancyrillic=1227;e.Cheverticalstrokecyrillic=1208;e.Chi=935;e.Chook=391;e.Circumflexsmall=63222;e.Cmonospace=65315;e.Coarmenian=1361;e.Csmall=63331;e.D=68;e.DZ=497;e.DZcaron=452;e.Daarmenian=1332;e.Dafrican=393;e.Dcaron=270;e.Dcedilla=7696;e.Dcircle=9401;e.Dcircumflexbelow=7698;e.Dcroat=272;e.Ddotaccent=7690;e.Ddotbelow=7692;e.Decyrillic=1044;e.Deicoptic=1006;e.Delta=8710;e.Deltagreek=916;e.Dhook=394;e.Dieresis=63179;e.DieresisAcute=63180;e.DieresisGrave=63181;e.Dieresissmall=63400;e.Digammagreek=988;e.Djecyrillic=1026;e.Dlinebelow=7694;e.Dmonospace=65316;e.Dotaccentsmall=63223;e.Dslash=272;e.Dsmall=63332;e.Dtopbar=395;e.Dz=498;e.Dzcaron=453;e.Dzeabkhasiancyrillic=1248;e.Dzecyrillic=1029;e.Dzhecyrillic=1039;e.E=69;e.Eacute=201;e.Eacutesmall=63465;e.Ebreve=276;e.Ecaron=282;e.Ecedillabreve=7708;e.Echarmenian=1333;e.Ecircle=9402;e.Ecircumflex=202;e.Ecircumflexacute=7870;e.Ecircumflexbelow=7704;e.Ecircumflexdotbelow=7878;e.Ecircumflexgrave=7872;e.Ecircumflexhookabove=7874;e.Ecircumflexsmall=63466;e.Ecircumflextilde=7876;e.Ecyrillic=1028;e.Edblgrave=516;e.Edieresis=203;e.Edieresissmall=63467;e.Edot=278;e.Edotaccent=278;e.Edotbelow=7864;e.Efcyrillic=1060;e.Egrave=200;e.Egravesmall=63464;e.Eharmenian=1335;e.Ehookabove=7866;e.Eightroman=8551;e.Einvertedbreve=518;e.Eiotifiedcyrillic=1124;e.Elcyrillic=1051;e.Elevenroman=8554;e.Emacron=274;e.Emacronacute=7702;e.Emacrongrave=7700;e.Emcyrillic=1052;e.Emonospace=65317;e.Encyrillic=1053;e.Endescendercyrillic=1186;e.Eng=330;e.Enghecyrillic=1188;e.Enhookcyrillic=1223;e.Eogonek=280;e.Eopen=400;e.Epsilon=917;e.Epsilontonos=904;e.Ercyrillic=1056;e.Ereversed=398;e.Ereversedcyrillic=1069;e.Escyrillic=1057;e.Esdescendercyrillic=1194;e.Esh=425;e.Esmall=63333;e.Eta=919;e.Etarmenian=1336;e.Etatonos=905;e.Eth=208;e.Ethsmall=63472;e.Etilde=7868;e.Etildebelow=7706;e.Euro=8364;e.Ezh=439;e.Ezhcaron=494;e.Ezhreversed=440;e.F=70;e.Fcircle=9403;e.Fdotaccent=7710;e.Feharmenian=1366;e.Feicoptic=996;e.Fhook=401;e.Fitacyrillic=1138;e.Fiveroman=8548;e.Fmonospace=65318;e.Fourroman=8547;e.Fsmall=63334;e.G=71;e.GBsquare=13191;e.Gacute=500;e.Gamma=915;e.Gammaafrican=404;e.Gangiacoptic=1002;e.Gbreve=286;e.Gcaron=486;e.Gcedilla=290;e.Gcircle=9404;e.Gcircumflex=284;e.Gcommaaccent=290;e.Gdot=288;e.Gdotaccent=288;e.Gecyrillic=1043;e.Ghadarmenian=1346;e.Ghemiddlehookcyrillic=1172;e.Ghestrokecyrillic=1170;e.Gheupturncyrillic=1168;e.Ghook=403;e.Gimarmenian=1331;e.Gjecyrillic=1027;e.Gmacron=7712;e.Gmonospace=65319;e.Grave=63182;e.Gravesmall=63328;e.Gsmall=63335;e.Gsmallhook=667;e.Gstroke=484;e.H=72;e.H18533=9679;e.H18543=9642;e.H18551=9643;e.H22073=9633;e.HPsquare=13259;e.Haabkhasiancyrillic=1192;e.Hadescendercyrillic=1202;e.Hardsigncyrillic=1066;e.Hbar=294;e.Hbrevebelow=7722;e.Hcedilla=7720;e.Hcircle=9405;e.Hcircumflex=292;e.Hdieresis=7718;e.Hdotaccent=7714;e.Hdotbelow=7716;e.Hmonospace=65320;e.Hoarmenian=1344;e.Horicoptic=1e3;e.Hsmall=63336;e.Hungarumlaut=63183;e.Hungarumlautsmall=63224;e.Hzsquare=13200;e.I=73;e.IAcyrillic=1071;e.IJ=306;e.IUcyrillic=1070;e.Iacute=205;e.Iacutesmall=63469;e.Ibreve=300;e.Icaron=463;e.Icircle=9406;e.Icircumflex=206;e.Icircumflexsmall=63470;e.Icyrillic=1030;e.Idblgrave=520;e.Idieresis=207;e.Idieresisacute=7726;e.Idieresiscyrillic=1252;e.Idieresissmall=63471;e.Idot=304;e.Idotaccent=304;e.Idotbelow=7882;e.Iebrevecyrillic=1238;e.Iecyrillic=1045;e.Ifraktur=8465;e.Igrave=204;e.Igravesmall=63468;e.Ihookabove=7880;e.Iicyrillic=1048;e.Iinvertedbreve=522;e.Iishortcyrillic=1049;e.Imacron=298;e.Imacroncyrillic=1250;e.Imonospace=65321;e.Iniarmenian=1339;e.Iocyrillic=1025;e.Iogonek=302;e.Iota=921;e.Iotaafrican=406;e.Iotadieresis=938;e.Iotatonos=906;e.Ismall=63337;e.Istroke=407;e.Itilde=296;e.Itildebelow=7724;e.Izhitsacyrillic=1140;e.Izhitsadblgravecyrillic=1142;e.J=74;e.Jaarmenian=1345;e.Jcircle=9407;e.Jcircumflex=308;e.Jecyrillic=1032;e.Jheharmenian=1355;e.Jmonospace=65322;e.Jsmall=63338;e.K=75;e.KBsquare=13189;e.KKsquare=13261;e.Kabashkircyrillic=1184;e.Kacute=7728;e.Kacyrillic=1050;e.Kadescendercyrillic=1178;e.Kahookcyrillic=1219;e.Kappa=922;e.Kastrokecyrillic=1182;e.Kaverticalstrokecyrillic=1180;e.Kcaron=488;e.Kcedilla=310;e.Kcircle=9408;e.Kcommaaccent=310;e.Kdotbelow=7730;e.Keharmenian=1364;e.Kenarmenian=1343;e.Khacyrillic=1061;e.Kheicoptic=998;e.Khook=408;e.Kjecyrillic=1036;e.Klinebelow=7732;e.Kmonospace=65323;e.Koppacyrillic=1152;e.Koppagreek=990;e.Ksicyrillic=1134;e.Ksmall=63339;e.L=76;e.LJ=455;e.LL=63167;e.Lacute=313;e.Lambda=923;e.Lcaron=317;e.Lcedilla=315;e.Lcircle=9409;e.Lcircumflexbelow=7740;e.Lcommaaccent=315;e.Ldot=319;e.Ldotaccent=319;e.Ldotbelow=7734;e.Ldotbelowmacron=7736;e.Liwnarmenian=1340;e.Lj=456;e.Ljecyrillic=1033;e.Llinebelow=7738;e.Lmonospace=65324;e.Lslash=321;e.Lslashsmall=63225;e.Lsmall=63340;e.M=77;e.MBsquare=13190;e.Macron=63184;e.Macronsmall=63407;e.Macute=7742;e.Mcircle=9410;e.Mdotaccent=7744;e.Mdotbelow=7746;e.Menarmenian=1348;e.Mmonospace=65325;e.Msmall=63341;e.Mturned=412;e.Mu=924;e.N=78;e.NJ=458;e.Nacute=323;e.Ncaron=327;e.Ncedilla=325;e.Ncircle=9411;e.Ncircumflexbelow=7754;e.Ncommaaccent=325;e.Ndotaccent=7748;e.Ndotbelow=7750;e.Nhookleft=413;e.Nineroman=8552;e.Nj=459;e.Njecyrillic=1034;e.Nlinebelow=7752;e.Nmonospace=65326;e.Nowarmenian=1350;e.Nsmall=63342;e.Ntilde=209;e.Ntildesmall=63473;e.Nu=925;e.O=79;e.OE=338;e.OEsmall=63226;e.Oacute=211;e.Oacutesmall=63475;e.Obarredcyrillic=1256;e.Obarreddieresiscyrillic=1258;e.Obreve=334;e.Ocaron=465;e.Ocenteredtilde=415;e.Ocircle=9412;e.Ocircumflex=212;e.Ocircumflexacute=7888;e.Ocircumflexdotbelow=7896;e.Ocircumflexgrave=7890;e.Ocircumflexhookabove=7892;e.Ocircumflexsmall=63476;e.Ocircumflextilde=7894;e.Ocyrillic=1054;e.Odblacute=336;e.Odblgrave=524;e.Odieresis=214;e.Odieresiscyrillic=1254;e.Odieresissmall=63478;e.Odotbelow=7884;e.Ogoneksmall=63227;e.Ograve=210;e.Ogravesmall=63474;e.Oharmenian=1365;e.Ohm=8486;e.Ohookabove=7886;e.Ohorn=416;e.Ohornacute=7898;e.Ohorndotbelow=7906;e.Ohorngrave=7900;e.Ohornhookabove=7902;e.Ohorntilde=7904;e.Ohungarumlaut=336;e.Oi=418;e.Oinvertedbreve=526;e.Omacron=332;e.Omacronacute=7762;e.Omacrongrave=7760;e.Omega=8486;e.Omegacyrillic=1120;e.Omegagreek=937;e.Omegaroundcyrillic=1146;e.Omegatitlocyrillic=1148;e.Omegatonos=911;e.Omicron=927;e.Omicrontonos=908;e.Omonospace=65327;e.Oneroman=8544;e.Oogonek=490;e.Oogonekmacron=492;e.Oopen=390;e.Oslash=216;e.Oslashacute=510;e.Oslashsmall=63480;e.Osmall=63343;e.Ostrokeacute=510;e.Otcyrillic=1150;e.Otilde=213;e.Otildeacute=7756;e.Otildedieresis=7758;e.Otildesmall=63477;e.P=80;e.Pacute=7764;e.Pcircle=9413;e.Pdotaccent=7766;e.Pecyrillic=1055;e.Peharmenian=1354;e.Pemiddlehookcyrillic=1190;e.Phi=934;e.Phook=420;e.Pi=928;e.Piwrarmenian=1363;e.Pmonospace=65328;e.Psi=936;e.Psicyrillic=1136;e.Psmall=63344;e.Q=81;e.Qcircle=9414;e.Qmonospace=65329;e.Qsmall=63345;e.R=82;e.Raarmenian=1356;e.Racute=340;e.Rcaron=344;e.Rcedilla=342;e.Rcircle=9415;e.Rcommaaccent=342;e.Rdblgrave=528;e.Rdotaccent=7768;e.Rdotbelow=7770;e.Rdotbelowmacron=7772;e.Reharmenian=1360;e.Rfraktur=8476;e.Rho=929;e.Ringsmall=63228;e.Rinvertedbreve=530;e.Rlinebelow=7774;e.Rmonospace=65330;e.Rsmall=63346;e.Rsmallinverted=641;e.Rsmallinvertedsuperior=694;e.S=83;e.SF010000=9484;e.SF020000=9492;e.SF030000=9488;e.SF040000=9496;e.SF050000=9532;e.SF060000=9516;e.SF070000=9524;e.SF080000=9500;e.SF090000=9508;e.SF100000=9472;e.SF110000=9474;e.SF190000=9569;e.SF200000=9570;e.SF210000=9558;e.SF220000=9557;e.SF230000=9571;e.SF240000=9553;e.SF250000=9559;e.SF260000=9565;e.SF270000=9564;e.SF280000=9563;e.SF360000=9566;e.SF370000=9567;e.SF380000=9562;e.SF390000=9556;e.SF400000=9577;e.SF410000=9574;e.SF420000=9568;e.SF430000=9552;e.SF440000=9580;e.SF450000=9575;e.SF460000=9576;e.SF470000=9572;e.SF480000=9573;e.SF490000=9561;e.SF500000=9560;e.SF510000=9554;e.SF520000=9555;e.SF530000=9579;e.SF540000=9578;e.Sacute=346;e.Sacutedotaccent=7780;e.Sampigreek=992;e.Scaron=352;e.Scarondotaccent=7782;e.Scaronsmall=63229;e.Scedilla=350;e.Schwa=399;e.Schwacyrillic=1240;e.Schwadieresiscyrillic=1242;e.Scircle=9416;e.Scircumflex=348;e.Scommaaccent=536;e.Sdotaccent=7776;e.Sdotbelow=7778;e.Sdotbelowdotaccent=7784;e.Seharmenian=1357;e.Sevenroman=8550;e.Shaarmenian=1351;e.Shacyrillic=1064;e.Shchacyrillic=1065;e.Sheicoptic=994;e.Shhacyrillic=1210;e.Shimacoptic=1004;e.Sigma=931;e.Sixroman=8549;e.Smonospace=65331;e.Softsigncyrillic=1068;e.Ssmall=63347;e.Stigmagreek=986;e.T=84;e.Tau=932;e.Tbar=358;e.Tcaron=356;e.Tcedilla=354;e.Tcircle=9417;e.Tcircumflexbelow=7792;e.Tcommaaccent=354;e.Tdotaccent=7786;e.Tdotbelow=7788;e.Tecyrillic=1058;e.Tedescendercyrillic=1196;e.Tenroman=8553;e.Tetsecyrillic=1204;e.Theta=920;e.Thook=428;e.Thorn=222;e.Thornsmall=63486;e.Threeroman=8546;e.Tildesmall=63230;e.Tiwnarmenian=1359;e.Tlinebelow=7790;e.Tmonospace=65332;e.Toarmenian=1337;e.Tonefive=444;e.Tonesix=388;e.Tonetwo=423;e.Tretroflexhook=430;e.Tsecyrillic=1062;e.Tshecyrillic=1035;e.Tsmall=63348;e.Twelveroman=8555;e.Tworoman=8545;e.U=85;e.Uacute=218;e.Uacutesmall=63482;e.Ubreve=364;e.Ucaron=467;e.Ucircle=9418;e.Ucircumflex=219;e.Ucircumflexbelow=7798;e.Ucircumflexsmall=63483;e.Ucyrillic=1059;e.Udblacute=368;e.Udblgrave=532;e.Udieresis=220;e.Udieresisacute=471;e.Udieresisbelow=7794;e.Udieresiscaron=473;e.Udieresiscyrillic=1264;e.Udieresisgrave=475;e.Udieresismacron=469;e.Udieresissmall=63484;e.Udotbelow=7908;e.Ugrave=217;e.Ugravesmall=63481;e.Uhookabove=7910;e.Uhorn=431;e.Uhornacute=7912;e.Uhorndotbelow=7920;e.Uhorngrave=7914;e.Uhornhookabove=7916;e.Uhorntilde=7918;e.Uhungarumlaut=368;e.Uhungarumlautcyrillic=1266;e.Uinvertedbreve=534;e.Ukcyrillic=1144;e.Umacron=362;e.Umacroncyrillic=1262;e.Umacrondieresis=7802;e.Umonospace=65333;e.Uogonek=370;e.Upsilon=933;e.Upsilon1=978;e.Upsilonacutehooksymbolgreek=979;e.Upsilonafrican=433;e.Upsilondieresis=939;e.Upsilondieresishooksymbolgreek=980;e.Upsilonhooksymbol=978;e.Upsilontonos=910;e.Uring=366;e.Ushortcyrillic=1038;e.Usmall=63349;e.Ustraightcyrillic=1198;e.Ustraightstrokecyrillic=1200;e.Utilde=360;e.Utildeacute=7800;e.Utildebelow=7796;e.V=86;e.Vcircle=9419;e.Vdotbelow=7806;e.Vecyrillic=1042;e.Vewarmenian=1358;e.Vhook=434;e.Vmonospace=65334;e.Voarmenian=1352;e.Vsmall=63350;e.Vtilde=7804;e.W=87;e.Wacute=7810;e.Wcircle=9420;e.Wcircumflex=372;e.Wdieresis=7812;e.Wdotaccent=7814;e.Wdotbelow=7816;e.Wgrave=7808;e.Wmonospace=65335;e.Wsmall=63351;e.X=88;e.Xcircle=9421;e.Xdieresis=7820;e.Xdotaccent=7818;e.Xeharmenian=1341;e.Xi=926;e.Xmonospace=65336;e.Xsmall=63352;e.Y=89;e.Yacute=221;e.Yacutesmall=63485;e.Yatcyrillic=1122;e.Ycircle=9422;e.Ycircumflex=374;e.Ydieresis=376;e.Ydieresissmall=63487;e.Ydotaccent=7822;e.Ydotbelow=7924;e.Yericyrillic=1067;e.Yerudieresiscyrillic=1272;e.Ygrave=7922;e.Yhook=435;e.Yhookabove=7926;e.Yiarmenian=1349;e.Yicyrillic=1031;e.Yiwnarmenian=1362;e.Ymonospace=65337;e.Ysmall=63353;e.Ytilde=7928;e.Yusbigcyrillic=1130;e.Yusbigiotifiedcyrillic=1132;e.Yuslittlecyrillic=1126;e.Yuslittleiotifiedcyrillic=1128;e.Z=90;e.Zaarmenian=1334;e.Zacute=377;e.Zcaron=381;e.Zcaronsmall=63231;e.Zcircle=9423;e.Zcircumflex=7824;e.Zdot=379;e.Zdotaccent=379;e.Zdotbelow=7826;e.Zecyrillic=1047;e.Zedescendercyrillic=1176;e.Zedieresiscyrillic=1246;e.Zeta=918;e.Zhearmenian=1338;e.Zhebrevecyrillic=1217;e.Zhecyrillic=1046;e.Zhedescendercyrillic=1174;e.Zhedieresiscyrillic=1244;e.Zlinebelow=7828;e.Zmonospace=65338;e.Zsmall=63354;e.Zstroke=437;e.a=97;e.aabengali=2438;e.aacute=225;e.aadeva=2310;e.aagujarati=2694;e.aagurmukhi=2566;e.aamatragurmukhi=2622;e.aarusquare=13059;e.aavowelsignbengali=2494;e.aavowelsigndeva=2366;e.aavowelsigngujarati=2750;e.abbreviationmarkarmenian=1375;e.abbreviationsigndeva=2416;e.abengali=2437;e.abopomofo=12570;e.abreve=259;e.abreveacute=7855;e.abrevecyrillic=1233;e.abrevedotbelow=7863;e.abrevegrave=7857;e.abrevehookabove=7859;e.abrevetilde=7861;e.acaron=462;e.acircle=9424;e.acircumflex=226;e.acircumflexacute=7845;e.acircumflexdotbelow=7853;e.acircumflexgrave=7847;e.acircumflexhookabove=7849;e.acircumflextilde=7851;e.acute=180;e.acutebelowcmb=791;e.acutecmb=769;e.acutecomb=769;e.acutedeva=2388;e.acutelowmod=719;e.acutetonecmb=833;e.acyrillic=1072;e.adblgrave=513;e.addakgurmukhi=2673;e.adeva=2309;e.adieresis=228;e.adieresiscyrillic=1235;e.adieresismacron=479;e.adotbelow=7841;e.adotmacron=481;e.ae=230;e.aeacute=509;e.aekorean=12624;e.aemacron=483;e.afii00208=8213;e.afii08941=8356;e.afii10017=1040;e.afii10018=1041;e.afii10019=1042;e.afii10020=1043;e.afii10021=1044;e.afii10022=1045;e.afii10023=1025;e.afii10024=1046;e.afii10025=1047;e.afii10026=1048;e.afii10027=1049;e.afii10028=1050;e.afii10029=1051;e.afii10030=1052;e.afii10031=1053;e.afii10032=1054;e.afii10033=1055;e.afii10034=1056;e.afii10035=1057;e.afii10036=1058;e.afii10037=1059;e.afii10038=1060;e.afii10039=1061;e.afii10040=1062;e.afii10041=1063;e.afii10042=1064;e.afii10043=1065;e.afii10044=1066;e.afii10045=1067;e.afii10046=1068;e.afii10047=1069;e.afii10048=1070;e.afii10049=1071;e.afii10050=1168;e.afii10051=1026;e.afii10052=1027;e.afii10053=1028;e.afii10054=1029;e.afii10055=1030;e.afii10056=1031;e.afii10057=1032;e.afii10058=1033;e.afii10059=1034;e.afii10060=1035;e.afii10061=1036;e.afii10062=1038;e.afii10063=63172;e.afii10064=63173;e.afii10065=1072;e.afii10066=1073;e.afii10067=1074;e.afii10068=1075;e.afii10069=1076;e.afii10070=1077;e.afii10071=1105;e.afii10072=1078;e.afii10073=1079;e.afii10074=1080;e.afii10075=1081;e.afii10076=1082;e.afii10077=1083;e.afii10078=1084;e.afii10079=1085;e.afii10080=1086;e.afii10081=1087;e.afii10082=1088;e.afii10083=1089;e.afii10084=1090;e.afii10085=1091;e.afii10086=1092;e.afii10087=1093;e.afii10088=1094;e.afii10089=1095;e.afii10090=1096;e.afii10091=1097;e.afii10092=1098;e.afii10093=1099;e.afii10094=1100;e.afii10095=1101;e.afii10096=1102;e.afii10097=1103;e.afii10098=1169;e.afii10099=1106;e.afii10100=1107;e.afii10101=1108;e.afii10102=1109;e.afii10103=1110;e.afii10104=1111;e.afii10105=1112;e.afii10106=1113;e.afii10107=1114;e.afii10108=1115;e.afii10109=1116;e.afii10110=1118;e.afii10145=1039;e.afii10146=1122;e.afii10147=1138;e.afii10148=1140;e.afii10192=63174;e.afii10193=1119;e.afii10194=1123;e.afii10195=1139;e.afii10196=1141;e.afii10831=63175;e.afii10832=63176;e.afii10846=1241;e.afii299=8206;e.afii300=8207;e.afii301=8205;e.afii57381=1642;e.afii57388=1548;e.afii57392=1632;e.afii57393=1633;e.afii57394=1634;e.afii57395=1635;e.afii57396=1636;e.afii57397=1637;e.afii57398=1638;e.afii57399=1639;e.afii57400=1640;e.afii57401=1641;e.afii57403=1563;e.afii57407=1567;e.afii57409=1569;e.afii57410=1570;e.afii57411=1571;e.afii57412=1572;e.afii57413=1573;e.afii57414=1574;e.afii57415=1575;e.afii57416=1576;e.afii57417=1577;e.afii57418=1578;e.afii57419=1579;e.afii57420=1580;e.afii57421=1581;e.afii57422=1582;e.afii57423=1583;e.afii57424=1584;e.afii57425=1585;e.afii57426=1586;e.afii57427=1587;e.afii57428=1588;e.afii57429=1589;e.afii57430=1590;e.afii57431=1591;e.afii57432=1592;e.afii57433=1593;e.afii57434=1594;e.afii57440=1600;e.afii57441=1601;e.afii57442=1602;e.afii57443=1603;e.afii57444=1604;e.afii57445=1605;e.afii57446=1606;e.afii57448=1608;e.afii57449=1609;e.afii57450=1610;e.afii57451=1611;e.afii57452=1612;e.afii57453=1613;e.afii57454=1614;e.afii57455=1615;e.afii57456=1616;e.afii57457=1617;e.afii57458=1618;e.afii57470=1607;e.afii57505=1700;e.afii57506=1662;e.afii57507=1670;e.afii57508=1688;e.afii57509=1711;e.afii57511=1657;e.afii57512=1672;e.afii57513=1681;e.afii57514=1722;e.afii57519=1746;e.afii57534=1749;e.afii57636=8362;e.afii57645=1470;e.afii57658=1475;e.afii57664=1488;e.afii57665=1489;e.afii57666=1490;e.afii57667=1491;e.afii57668=1492;e.afii57669=1493;e.afii57670=1494;e.afii57671=1495;e.afii57672=1496;e.afii57673=1497;e.afii57674=1498;e.afii57675=1499;e.afii57676=1500;e.afii57677=1501;e.afii57678=1502;e.afii57679=1503;e.afii57680=1504;e.afii57681=1505;e.afii57682=1506;e.afii57683=1507;e.afii57684=1508;e.afii57685=1509;e.afii57686=1510;e.afii57687=1511;e.afii57688=1512;e.afii57689=1513;e.afii57690=1514;e.afii57694=64298;e.afii57695=64299;e.afii57700=64331;e.afii57705=64287;e.afii57716=1520;e.afii57717=1521;e.afii57718=1522;e.afii57723=64309;e.afii57793=1460;e.afii57794=1461;e.afii57795=1462;e.afii57796=1467;e.afii57797=1464;e.afii57798=1463;e.afii57799=1456;e.afii57800=1458;e.afii57801=1457;e.afii57802=1459;e.afii57803=1474;e.afii57804=1473;e.afii57806=1465;e.afii57807=1468;e.afii57839=1469;e.afii57841=1471;e.afii57842=1472;e.afii57929=700;e.afii61248=8453;e.afii61289=8467;e.afii61352=8470;e.afii61573=8236;e.afii61574=8237;e.afii61575=8238;e.afii61664=8204;e.afii63167=1645;e.afii64937=701;e.agrave=224;e.agujarati=2693;e.agurmukhi=2565;e.ahiragana=12354;e.ahookabove=7843;e.aibengali=2448;e.aibopomofo=12574;e.aideva=2320;e.aiecyrillic=1237;e.aigujarati=2704;e.aigurmukhi=2576;e.aimatragurmukhi=2632;e.ainarabic=1593;e.ainfinalarabic=65226;e.aininitialarabic=65227;e.ainmedialarabic=65228 +;e.ainvertedbreve=515;e.aivowelsignbengali=2504;e.aivowelsigndeva=2376;e.aivowelsigngujarati=2760;e.akatakana=12450;e.akatakanahalfwidth=65393;e.akorean=12623;e.alef=1488;e.alefarabic=1575;e.alefdageshhebrew=64304;e.aleffinalarabic=65166;e.alefhamzaabovearabic=1571;e.alefhamzaabovefinalarabic=65156;e.alefhamzabelowarabic=1573;e.alefhamzabelowfinalarabic=65160;e.alefhebrew=1488;e.aleflamedhebrew=64335;e.alefmaddaabovearabic=1570;e.alefmaddaabovefinalarabic=65154;e.alefmaksuraarabic=1609;e.alefmaksurafinalarabic=65264;e.alefmaksurainitialarabic=65267;e.alefmaksuramedialarabic=65268;e.alefpatahhebrew=64302;e.alefqamatshebrew=64303;e.aleph=8501;e.allequal=8780;e.alpha=945;e.alphatonos=940;e.amacron=257;e.amonospace=65345;e.ampersand=38;e.ampersandmonospace=65286;e.ampersandsmall=63270;e.amsquare=13250;e.anbopomofo=12578;e.angbopomofo=12580;e.angbracketleft=12296;e.angbracketright=12297;e.angkhankhuthai=3674;e.angle=8736;e.anglebracketleft=12296;e.anglebracketleftvertical=65087;e.anglebracketright=12297;e.anglebracketrightvertical=65088;e.angleleft=9001;e.angleright=9002;e.angstrom=8491;e.anoteleia=903;e.anudattadeva=2386;e.anusvarabengali=2434;e.anusvaradeva=2306;e.anusvaragujarati=2690;e.aogonek=261;e.apaatosquare=13056;e.aparen=9372;e.apostrophearmenian=1370;e.apostrophemod=700;e.apple=63743;e.approaches=8784;e.approxequal=8776;e.approxequalorimage=8786;e.approximatelyequal=8773;e.araeaekorean=12686;e.araeakorean=12685;e.arc=8978;e.arighthalfring=7834;e.aring=229;e.aringacute=507;e.aringbelow=7681;e.arrowboth=8596;e.arrowdashdown=8675;e.arrowdashleft=8672;e.arrowdashright=8674;e.arrowdashup=8673;e.arrowdblboth=8660;e.arrowdbldown=8659;e.arrowdblleft=8656;e.arrowdblright=8658;e.arrowdblup=8657;e.arrowdown=8595;e.arrowdownleft=8601;e.arrowdownright=8600;e.arrowdownwhite=8681;e.arrowheaddownmod=709;e.arrowheadleftmod=706;e.arrowheadrightmod=707;e.arrowheadupmod=708;e.arrowhorizex=63719;e.arrowleft=8592;e.arrowleftdbl=8656;e.arrowleftdblstroke=8653;e.arrowleftoverright=8646;e.arrowleftwhite=8678;e.arrowright=8594;e.arrowrightdblstroke=8655;e.arrowrightheavy=10142;e.arrowrightoverleft=8644;e.arrowrightwhite=8680;e.arrowtableft=8676;e.arrowtabright=8677;e.arrowup=8593;e.arrowupdn=8597;e.arrowupdnbse=8616;e.arrowupdownbase=8616;e.arrowupleft=8598;e.arrowupleftofdown=8645;e.arrowupright=8599;e.arrowupwhite=8679;e.arrowvertex=63718;e.asciicircum=94;e.asciicircummonospace=65342;e.asciitilde=126;e.asciitildemonospace=65374;e.ascript=593;e.ascriptturned=594;e.asmallhiragana=12353;e.asmallkatakana=12449;e.asmallkatakanahalfwidth=65383;e.asterisk=42;e.asteriskaltonearabic=1645;e.asteriskarabic=1645;e.asteriskmath=8727;e.asteriskmonospace=65290;e.asterisksmall=65121;e.asterism=8258;e.asuperior=63209;e.asymptoticallyequal=8771;e.at=64;e.atilde=227;e.atmonospace=65312;e.atsmall=65131;e.aturned=592;e.aubengali=2452;e.aubopomofo=12576;e.audeva=2324;e.augujarati=2708;e.augurmukhi=2580;e.aulengthmarkbengali=2519;e.aumatragurmukhi=2636;e.auvowelsignbengali=2508;e.auvowelsigndeva=2380;e.auvowelsigngujarati=2764;e.avagrahadeva=2365;e.aybarmenian=1377;e.ayin=1506;e.ayinaltonehebrew=64288;e.ayinhebrew=1506;e.b=98;e.babengali=2476;e.backslash=92;e.backslashmonospace=65340;e.badeva=2348;e.bagujarati=2732;e.bagurmukhi=2604;e.bahiragana=12400;e.bahtthai=3647;e.bakatakana=12496;e.bar=124;e.barmonospace=65372;e.bbopomofo=12549;e.bcircle=9425;e.bdotaccent=7683;e.bdotbelow=7685;e.beamedsixteenthnotes=9836;e.because=8757;e.becyrillic=1073;e.beharabic=1576;e.behfinalarabic=65168;e.behinitialarabic=65169;e.behiragana=12409;e.behmedialarabic=65170;e.behmeeminitialarabic=64671;e.behmeemisolatedarabic=64520;e.behnoonfinalarabic=64621;e.bekatakana=12505;e.benarmenian=1378;e.bet=1489;e.beta=946;e.betasymbolgreek=976;e.betdagesh=64305;e.betdageshhebrew=64305;e.bethebrew=1489;e.betrafehebrew=64332;e.bhabengali=2477;e.bhadeva=2349;e.bhagujarati=2733;e.bhagurmukhi=2605;e.bhook=595;e.bihiragana=12403;e.bikatakana=12499;e.bilabialclick=664;e.bindigurmukhi=2562;e.birusquare=13105;e.blackcircle=9679;e.blackdiamond=9670;e.blackdownpointingtriangle=9660;e.blackleftpointingpointer=9668;e.blackleftpointingtriangle=9664;e.blacklenticularbracketleft=12304;e.blacklenticularbracketleftvertical=65083;e.blacklenticularbracketright=12305;e.blacklenticularbracketrightvertical=65084;e.blacklowerlefttriangle=9699;e.blacklowerrighttriangle=9698;e.blackrectangle=9644;e.blackrightpointingpointer=9658;e.blackrightpointingtriangle=9654;e.blacksmallsquare=9642;e.blacksmilingface=9787;e.blacksquare=9632;e.blackstar=9733;e.blackupperlefttriangle=9700;e.blackupperrighttriangle=9701;e.blackuppointingsmalltriangle=9652;e.blackuppointingtriangle=9650;e.blank=9251;e.blinebelow=7687;e.block=9608;e.bmonospace=65346;e.bobaimaithai=3610;e.bohiragana=12412;e.bokatakana=12508;e.bparen=9373;e.bqsquare=13251;e.braceex=63732;e.braceleft=123;e.braceleftbt=63731;e.braceleftmid=63730;e.braceleftmonospace=65371;e.braceleftsmall=65115;e.bracelefttp=63729;e.braceleftvertical=65079;e.braceright=125;e.bracerightbt=63742;e.bracerightmid=63741;e.bracerightmonospace=65373;e.bracerightsmall=65116;e.bracerighttp=63740;e.bracerightvertical=65080;e.bracketleft=91;e.bracketleftbt=63728;e.bracketleftex=63727;e.bracketleftmonospace=65339;e.bracketlefttp=63726;e.bracketright=93;e.bracketrightbt=63739;e.bracketrightex=63738;e.bracketrightmonospace=65341;e.bracketrighttp=63737;e.breve=728;e.brevebelowcmb=814;e.brevecmb=774;e.breveinvertedbelowcmb=815;e.breveinvertedcmb=785;e.breveinverteddoublecmb=865;e.bridgebelowcmb=810;e.bridgeinvertedbelowcmb=826;e.brokenbar=166;e.bstroke=384;e.bsuperior=63210;e.btopbar=387;e.buhiragana=12406;e.bukatakana=12502;e.bullet=8226;e.bulletinverse=9688;e.bulletoperator=8729;e.bullseye=9678;e.c=99;e.caarmenian=1390;e.cabengali=2458;e.cacute=263;e.cadeva=2330;e.cagujarati=2714;e.cagurmukhi=2586;e.calsquare=13192;e.candrabindubengali=2433;e.candrabinducmb=784;e.candrabindudeva=2305;e.candrabindugujarati=2689;e.capslock=8682;e.careof=8453;e.caron=711;e.caronbelowcmb=812;e.caroncmb=780;e.carriagereturn=8629;e.cbopomofo=12568;e.ccaron=269;e.ccedilla=231;e.ccedillaacute=7689;e.ccircle=9426;e.ccircumflex=265;e.ccurl=597;e.cdot=267;e.cdotaccent=267;e.cdsquare=13253;e.cedilla=184;e.cedillacmb=807;e.cent=162;e.centigrade=8451;e.centinferior=63199;e.centmonospace=65504;e.centoldstyle=63394;e.centsuperior=63200;e.chaarmenian=1401;e.chabengali=2459;e.chadeva=2331;e.chagujarati=2715;e.chagurmukhi=2587;e.chbopomofo=12564;e.cheabkhasiancyrillic=1213;e.checkmark=10003;e.checyrillic=1095;e.chedescenderabkhasiancyrillic=1215;e.chedescendercyrillic=1207;e.chedieresiscyrillic=1269;e.cheharmenian=1395;e.chekhakassiancyrillic=1228;e.cheverticalstrokecyrillic=1209;e.chi=967;e.chieuchacirclekorean=12919;e.chieuchaparenkorean=12823;e.chieuchcirclekorean=12905;e.chieuchkorean=12618;e.chieuchparenkorean=12809;e.chochangthai=3594;e.chochanthai=3592;e.chochingthai=3593;e.chochoethai=3596;e.chook=392;e.cieucacirclekorean=12918;e.cieucaparenkorean=12822;e.cieuccirclekorean=12904;e.cieuckorean=12616;e.cieucparenkorean=12808;e.cieucuparenkorean=12828;e.circle=9675;e.circlecopyrt=169;e.circlemultiply=8855;e.circleot=8857;e.circleplus=8853;e.circlepostalmark=12342;e.circlewithlefthalfblack=9680;e.circlewithrighthalfblack=9681;e.circumflex=710;e.circumflexbelowcmb=813;e.circumflexcmb=770;e.clear=8999;e.clickalveolar=450;e.clickdental=448;e.clicklateral=449;e.clickretroflex=451;e.club=9827;e.clubsuitblack=9827;e.clubsuitwhite=9831;e.cmcubedsquare=13220;e.cmonospace=65347;e.cmsquaredsquare=13216;e.coarmenian=1409;e.colon=58;e.colonmonetary=8353;e.colonmonospace=65306;e.colonsign=8353;e.colonsmall=65109;e.colontriangularhalfmod=721;e.colontriangularmod=720;e.comma=44;e.commaabovecmb=787;e.commaaboverightcmb=789;e.commaaccent=63171;e.commaarabic=1548;e.commaarmenian=1373;e.commainferior=63201;e.commamonospace=65292;e.commareversedabovecmb=788;e.commareversedmod=701;e.commasmall=65104;e.commasuperior=63202;e.commaturnedabovecmb=786;e.commaturnedmod=699;e.compass=9788;e.congruent=8773;e.contourintegral=8750;e.control=8963;e.controlACK=6;e.controlBEL=7;e.controlBS=8;e.controlCAN=24;e.controlCR=13;e.controlDC1=17;e.controlDC2=18;e.controlDC3=19;e.controlDC4=20;e.controlDEL=127;e.controlDLE=16;e.controlEM=25;e.controlENQ=5;e.controlEOT=4;e.controlESC=27;e.controlETB=23;e.controlETX=3;e.controlFF=12;e.controlFS=28;e.controlGS=29;e.controlHT=9;e.controlLF=10;e.controlNAK=21;e.controlNULL=0;e.controlRS=30;e.controlSI=15;e.controlSO=14;e.controlSOT=2;e.controlSTX=1;e.controlSUB=26;e.controlSYN=22;e.controlUS=31;e.controlVT=11;e.copyright=169;e.copyrightsans=63721;e.copyrightserif=63193;e.cornerbracketleft=12300;e.cornerbracketlefthalfwidth=65378;e.cornerbracketleftvertical=65089;e.cornerbracketright=12301;e.cornerbracketrighthalfwidth=65379;e.cornerbracketrightvertical=65090;e.corporationsquare=13183;e.cosquare=13255;e.coverkgsquare=13254;e.cparen=9374;e.cruzeiro=8354;e.cstretched=663;e.curlyand=8911;e.curlyor=8910;e.currency=164;e.cyrBreve=63185;e.cyrFlex=63186;e.cyrbreve=63188;e.cyrflex=63189;e.d=100;e.daarmenian=1380;e.dabengali=2470;e.dadarabic=1590;e.dadeva=2342;e.dadfinalarabic=65214;e.dadinitialarabic=65215;e.dadmedialarabic=65216;e.dagesh=1468;e.dageshhebrew=1468;e.dagger=8224;e.daggerdbl=8225;e.dagujarati=2726;e.dagurmukhi=2598;e.dahiragana=12384;e.dakatakana=12480;e.dalarabic=1583;e.dalet=1491;e.daletdagesh=64307;e.daletdageshhebrew=64307;e.dalethebrew=1491;e.dalfinalarabic=65194;e.dammaarabic=1615;e.dammalowarabic=1615;e.dammatanaltonearabic=1612;e.dammatanarabic=1612;e.danda=2404;e.dargahebrew=1447;e.dargalefthebrew=1447;e.dasiapneumatacyrilliccmb=1157;e.dblGrave=63187;e.dblanglebracketleft=12298;e.dblanglebracketleftvertical=65085;e.dblanglebracketright=12299;e.dblanglebracketrightvertical=65086;e.dblarchinvertedbelowcmb=811;e.dblarrowleft=8660;e.dblarrowright=8658;e.dbldanda=2405;e.dblgrave=63190;e.dblgravecmb=783;e.dblintegral=8748;e.dbllowline=8215;e.dbllowlinecmb=819;e.dbloverlinecmb=831;e.dblprimemod=698;e.dblverticalbar=8214;e.dblverticallineabovecmb=782;e.dbopomofo=12553;e.dbsquare=13256;e.dcaron=271;e.dcedilla=7697;e.dcircle=9427;e.dcircumflexbelow=7699;e.dcroat=273;e.ddabengali=2465;e.ddadeva=2337;e.ddagujarati=2721;e.ddagurmukhi=2593;e.ddalarabic=1672;e.ddalfinalarabic=64393;e.dddhadeva=2396;e.ddhabengali=2466;e.ddhadeva=2338;e.ddhagujarati=2722;e.ddhagurmukhi=2594;e.ddotaccent=7691;e.ddotbelow=7693;e.decimalseparatorarabic=1643;e.decimalseparatorpersian=1643;e.decyrillic=1076;e.degree=176;e.dehihebrew=1453;e.dehiragana=12391;e.deicoptic=1007;e.dekatakana=12487;e.deleteleft=9003;e.deleteright=8998;e.delta=948;e.deltaturned=397;e.denominatorminusonenumeratorbengali=2552;e.dezh=676;e.dhabengali=2471;e.dhadeva=2343;e.dhagujarati=2727;e.dhagurmukhi=2599;e.dhook=599;e.dialytikatonos=901;e.dialytikatonoscmb=836;e.diamond=9830;e.diamondsuitwhite=9826;e.dieresis=168;e.dieresisacute=63191;e.dieresisbelowcmb=804;e.dieresiscmb=776;e.dieresisgrave=63192;e.dieresistonos=901;e.dihiragana=12386;e.dikatakana=12482;e.dittomark=12291;e.divide=247;e.divides=8739;e.divisionslash=8725;e.djecyrillic=1106;e.dkshade=9619;e.dlinebelow=7695;e.dlsquare=13207;e.dmacron=273;e.dmonospace=65348;e.dnblock=9604;e.dochadathai=3598;e.dodekthai=3604;e.dohiragana=12393;e.dokatakana=12489;e.dollar=36;e.dollarinferior=63203;e.dollarmonospace=65284;e.dollaroldstyle=63268;e.dollarsmall=65129;e.dollarsuperior=63204;e.dong=8363;e.dorusquare=13094;e.dotaccent=729;e.dotaccentcmb=775;e.dotbelowcmb=803;e.dotbelowcomb=803;e.dotkatakana=12539;e.dotlessi=305;e.dotlessj=63166;e.dotlessjstrokehook=644;e.dotmath=8901;e.dottedcircle=9676;e.doubleyodpatah=64287;e.doubleyodpatahhebrew=64287;e.downtackbelowcmb=798;e.downtackmod=725;e.dparen=9375;e.dsuperior=63211;e.dtail=598;e.dtopbar=396;e.duhiragana=12389;e.dukatakana=12485;e.dz=499;e.dzaltone=675;e.dzcaron=454;e.dzcurl=677;e.dzeabkhasiancyrillic=1249;e.dzecyrillic=1109;e.dzhecyrillic=1119;e.e=101;e.eacute=233;e.earth=9793;e.ebengali=2447;e.ebopomofo=12572;e.ebreve=277;e.ecandradeva=2317;e.ecandragujarati=2701;e.ecandravowelsigndeva=2373;e.ecandravowelsigngujarati=2757;e.ecaron=283;e.ecedillabreve=7709;e.echarmenian=1381;e.echyiwnarmenian=1415;e.ecircle=9428;e.ecircumflex=234;e.ecircumflexacute=7871;e.ecircumflexbelow=7705;e.ecircumflexdotbelow=7879;e.ecircumflexgrave=7873;e.ecircumflexhookabove=7875;e.ecircumflextilde=7877;e.ecyrillic=1108;e.edblgrave=517;e.edeva=2319;e.edieresis=235;e.edot=279;e.edotaccent=279;e.edotbelow=7865;e.eegurmukhi=2575;e.eematragurmukhi=2631;e.efcyrillic=1092;e.egrave=232;e.egujarati=2703;e.eharmenian=1383;e.ehbopomofo=12573;e.ehiragana=12360;e.ehookabove=7867;e.eibopomofo=12575;e.eight=56;e.eightarabic=1640;e.eightbengali=2542;e.eightcircle=9319;e.eightcircleinversesansserif=10129;e.eightdeva=2414;e.eighteencircle=9329;e.eighteenparen=9349;e.eighteenperiod=9369;e.eightgujarati=2798;e.eightgurmukhi=2670;e.eighthackarabic=1640;e.eighthangzhou=12328;e.eighthnotebeamed=9835;e.eightideographicparen=12839;e.eightinferior=8328;e.eightmonospace=65304;e.eightoldstyle=63288;e.eightparen=9339;e.eightperiod=9359;e.eightpersian=1784;e.eightroman=8567;e.eightsuperior=8312;e.eightthai=3672;e.einvertedbreve=519;e.eiotifiedcyrillic=1125;e.ekatakana=12456;e.ekatakanahalfwidth=65396;e.ekonkargurmukhi=2676;e.ekorean=12628;e.elcyrillic=1083;e.element=8712;e.elevencircle=9322;e.elevenparen=9342;e.elevenperiod=9362;e.elevenroman=8570;e.ellipsis=8230;e.ellipsisvertical=8942;e.emacron=275;e.emacronacute=7703;e.emacrongrave=7701;e.emcyrillic=1084;e.emdash=8212;e.emdashvertical=65073;e.emonospace=65349;e.emphasismarkarmenian=1371;e.emptyset=8709;e.enbopomofo=12579;e.encyrillic=1085;e.endash=8211;e.endashvertical=65074;e.endescendercyrillic=1187;e.eng=331;e.engbopomofo=12581;e.enghecyrillic=1189;e.enhookcyrillic=1224;e.enspace=8194;e.eogonek=281;e.eokorean=12627;e.eopen=603;e.eopenclosed=666;e.eopenreversed=604;e.eopenreversedclosed=606;e.eopenreversedhook=605;e.eparen=9376;e.epsilon=949;e.epsilontonos=941;e.equal=61;e.equalmonospace=65309;e.equalsmall=65126;e.equalsuperior=8316;e.equivalence=8801;e.erbopomofo=12582;e.ercyrillic=1088;e.ereversed=600;e.ereversedcyrillic=1101;e.escyrillic=1089;e.esdescendercyrillic=1195;e.esh=643;e.eshcurl=646;e.eshortdeva=2318;e.eshortvowelsigndeva=2374;e.eshreversedloop=426;e.eshsquatreversed=645;e.esmallhiragana=12359;e.esmallkatakana=12455;e.esmallkatakanahalfwidth=65386;e.estimated=8494;e.esuperior=63212;e.eta=951;e.etarmenian=1384;e.etatonos=942;e.eth=240;e.etilde=7869;e.etildebelow=7707;e.etnahtafoukhhebrew=1425;e.etnahtafoukhlefthebrew=1425;e.etnahtahebrew=1425;e.etnahtalefthebrew=1425;e.eturned=477;e.eukorean=12641;e.euro=8364;e.evowelsignbengali=2503;e.evowelsigndeva=2375;e.evowelsigngujarati=2759;e.exclam=33;e.exclamarmenian=1372;e.exclamdbl=8252;e.exclamdown=161;e.exclamdownsmall=63393;e.exclammonospace=65281;e.exclamsmall=63265;e.existential=8707;e.ezh=658;e.ezhcaron=495;e.ezhcurl=659;e.ezhreversed=441;e.ezhtail=442;e.f=102;e.fadeva=2398;e.fagurmukhi=2654;e.fahrenheit=8457;e.fathaarabic=1614;e.fathalowarabic=1614;e.fathatanarabic=1611;e.fbopomofo=12552;e.fcircle=9429;e.fdotaccent=7711;e.feharabic=1601;e.feharmenian=1414;e.fehfinalarabic=65234;e.fehinitialarabic=65235;e.fehmedialarabic=65236;e.feicoptic=997;e.female=9792;e.ff=64256;e.ffi=64259;e.ffl=64260;e.fi=64257;e.fifteencircle=9326;e.fifteenparen=9346;e.fifteenperiod=9366;e.figuredash=8210;e.filledbox=9632;e.filledrect=9644;e.finalkaf=1498;e.finalkafdagesh=64314;e.finalkafdageshhebrew=64314;e.finalkafhebrew=1498;e.finalmem=1501;e.finalmemhebrew=1501;e.finalnun=1503;e.finalnunhebrew=1503;e.finalpe=1507;e.finalpehebrew=1507;e.finaltsadi=1509;e.finaltsadihebrew=1509;e.firsttonechinese=713;e.fisheye=9673;e.fitacyrillic=1139;e.five=53;e.fivearabic=1637;e.fivebengali=2539;e.fivecircle=9316;e.fivecircleinversesansserif=10126;e.fivedeva=2411;e.fiveeighths=8541;e.fivegujarati=2795;e.fivegurmukhi=2667;e.fivehackarabic=1637;e.fivehangzhou=12325;e.fiveideographicparen=12836;e.fiveinferior=8325;e.fivemonospace=65301;e.fiveoldstyle=63285;e.fiveparen=9336;e.fiveperiod=9356;e.fivepersian=1781;e.fiveroman=8564;e.fivesuperior=8309;e.fivethai=3669;e.fl=64258;e.florin=402;e.fmonospace=65350;e.fmsquare=13209;e.fofanthai=3615;e.fofathai=3613;e.fongmanthai=3663;e.forall=8704;e.four=52;e.fourarabic=1636;e.fourbengali=2538;e.fourcircle=9315;e.fourcircleinversesansserif=10125;e.fourdeva=2410;e.fourgujarati=2794;e.fourgurmukhi=2666;e.fourhackarabic=1636;e.fourhangzhou=12324;e.fourideographicparen=12835;e.fourinferior=8324;e.fourmonospace=65300;e.fournumeratorbengali=2551;e.fouroldstyle=63284;e.fourparen=9335;e.fourperiod=9355;e.fourpersian=1780;e.fourroman=8563;e.foursuperior=8308;e.fourteencircle=9325;e.fourteenparen=9345;e.fourteenperiod=9365;e.fourthai=3668;e.fourthtonechinese=715;e.fparen=9377;e.fraction=8260;e.franc=8355;e.g=103;e.gabengali=2455;e.gacute=501;e.gadeva=2327;e.gafarabic=1711;e.gaffinalarabic=64403;e.gafinitialarabic=64404;e.gafmedialarabic=64405;e.gagujarati=2711;e.gagurmukhi=2583;e.gahiragana=12364;e.gakatakana=12460;e.gamma=947;e.gammalatinsmall=611;e.gammasuperior=736;e.gangiacoptic=1003;e.gbopomofo=12557;e.gbreve=287;e.gcaron=487;e.gcedilla=291;e.gcircle=9430;e.gcircumflex=285;e.gcommaaccent=291;e.gdot=289;e.gdotaccent=289;e.gecyrillic=1075;e.gehiragana=12370;e.gekatakana=12466;e.geometricallyequal=8785;e.gereshaccenthebrew=1436;e.gereshhebrew=1523;e.gereshmuqdamhebrew=1437;e.germandbls=223;e.gershayimaccenthebrew=1438;e.gershayimhebrew=1524;e.getamark=12307;e.ghabengali=2456;e.ghadarmenian=1394;e.ghadeva=2328;e.ghagujarati=2712;e.ghagurmukhi=2584;e.ghainarabic=1594;e.ghainfinalarabic=65230;e.ghaininitialarabic=65231;e.ghainmedialarabic=65232;e.ghemiddlehookcyrillic=1173;e.ghestrokecyrillic=1171;e.gheupturncyrillic=1169;e.ghhadeva=2394;e.ghhagurmukhi=2650;e.ghook=608;e.ghzsquare=13203;e.gihiragana=12366;e.gikatakana=12462;e.gimarmenian=1379;e.gimel=1490;e.gimeldagesh=64306;e.gimeldageshhebrew=64306;e.gimelhebrew=1490;e.gjecyrillic=1107;e.glottalinvertedstroke=446;e.glottalstop=660;e.glottalstopinverted=662;e.glottalstopmod=704;e.glottalstopreversed=661;e.glottalstopreversedmod=705;e.glottalstopreversedsuperior=740;e.glottalstopstroke=673;e.glottalstopstrokereversed=674;e.gmacron=7713;e.gmonospace=65351;e.gohiragana=12372;e.gokatakana=12468;e.gparen=9378;e.gpasquare=13228;e.gradient=8711;e.grave=96;e.gravebelowcmb=790;e.gravecmb=768;e.gravecomb=768;e.gravedeva=2387;e.gravelowmod=718;e.gravemonospace=65344;e.gravetonecmb=832;e.greater=62;e.greaterequal=8805;e.greaterequalorless=8923;e.greatermonospace=65310;e.greaterorequivalent=8819;e.greaterorless=8823;e.greateroverequal=8807;e.greatersmall=65125;e.gscript=609;e.gstroke=485;e.guhiragana=12368;e.guillemotleft=171;e.guillemotright=187;e.guilsinglleft=8249;e.guilsinglright=8250;e.gukatakana=12464;e.guramusquare=13080;e.gysquare=13257;e.h=104;e.haabkhasiancyrillic=1193;e.haaltonearabic=1729;e.habengali=2489;e.hadescendercyrillic=1203;e.hadeva=2361;e.hagujarati=2745;e.hagurmukhi=2617;e.haharabic=1581;e.hahfinalarabic=65186;e.hahinitialarabic=65187;e.hahiragana=12399;e.hahmedialarabic=65188;e.haitusquare=13098;e.hakatakana=12495;e.hakatakanahalfwidth=65418;e.halantgurmukhi=2637;e.hamzaarabic=1569;e.hamzalowarabic=1569;e.hangulfiller=12644;e.hardsigncyrillic=1098;e.harpoonleftbarbup=8636;e.harpoonrightbarbup=8640;e.hasquare=13258;e.hatafpatah=1458;e.hatafpatah16=1458;e.hatafpatah23=1458;e.hatafpatah2f=1458;e.hatafpatahhebrew=1458;e.hatafpatahnarrowhebrew=1458;e.hatafpatahquarterhebrew=1458;e.hatafpatahwidehebrew=1458;e.hatafqamats=1459;e.hatafqamats1b=1459;e.hatafqamats28=1459;e.hatafqamats34=1459;e.hatafqamatshebrew=1459;e.hatafqamatsnarrowhebrew=1459;e.hatafqamatsquarterhebrew=1459;e.hatafqamatswidehebrew=1459;e.hatafsegol=1457;e.hatafsegol17=1457;e.hatafsegol24=1457;e.hatafsegol30=1457;e.hatafsegolhebrew=1457;e.hatafsegolnarrowhebrew=1457;e.hatafsegolquarterhebrew=1457;e.hatafsegolwidehebrew=1457;e.hbar=295;e.hbopomofo=12559;e.hbrevebelow=7723;e.hcedilla=7721;e.hcircle=9431;e.hcircumflex=293;e.hdieresis=7719;e.hdotaccent=7715;e.hdotbelow=7717;e.he=1492;e.heart=9829;e.heartsuitblack=9829;e.heartsuitwhite=9825;e.hedagesh=64308;e.hedageshhebrew=64308;e.hehaltonearabic=1729;e.heharabic=1607;e.hehebrew=1492;e.hehfinalaltonearabic=64423;e.hehfinalalttwoarabic=65258;e.hehfinalarabic=65258;e.hehhamzaabovefinalarabic=64421;e.hehhamzaaboveisolatedarabic=64420;e.hehinitialaltonearabic=64424;e.hehinitialarabic=65259;e.hehiragana=12408;e.hehmedialaltonearabic=64425;e.hehmedialarabic=65260;e.heiseierasquare=13179;e.hekatakana=12504;e.hekatakanahalfwidth=65421;e.hekutaarusquare=13110;e.henghook=615;e.herutusquare=13113;e.het=1495;e.hethebrew=1495;e.hhook=614;e.hhooksuperior=689;e.hieuhacirclekorean=12923;e.hieuhaparenkorean=12827;e.hieuhcirclekorean=12909;e.hieuhkorean=12622;e.hieuhparenkorean=12813;e.hihiragana=12402;e.hikatakana=12498;e.hikatakanahalfwidth=65419;e.hiriq=1460;e.hiriq14=1460;e.hiriq21=1460;e.hiriq2d=1460;e.hiriqhebrew=1460;e.hiriqnarrowhebrew=1460;e.hiriqquarterhebrew=1460;e.hiriqwidehebrew=1460;e.hlinebelow=7830;e.hmonospace=65352;e.hoarmenian=1392;e.hohipthai=3627;e.hohiragana=12411;e.hokatakana=12507;e.hokatakanahalfwidth=65422;e.holam=1465;e.holam19=1465;e.holam26=1465;e.holam32=1465;e.holamhebrew=1465;e.holamnarrowhebrew=1465;e.holamquarterhebrew=1465;e.holamwidehebrew=1465;e.honokhukthai=3630;e.hookabovecomb=777;e.hookcmb=777;e.hookpalatalizedbelowcmb=801;e.hookretroflexbelowcmb=802;e.hoonsquare=13122;e.horicoptic=1001;e.horizontalbar=8213;e.horncmb=795;e.hotsprings=9832;e.house=8962;e.hparen=9379;e.hsuperior=688;e.hturned=613;e.huhiragana=12405;e.huiitosquare=13107;e.hukatakana=12501;e.hukatakanahalfwidth=65420;e.hungarumlaut=733;e.hungarumlautcmb=779;e.hv=405;e.hyphen=45;e.hypheninferior=63205;e.hyphenmonospace=65293;e.hyphensmall=65123;e.hyphensuperior=63206;e.hyphentwo=8208;e.i=105;e.iacute=237;e.iacyrillic=1103;e.ibengali=2439;e.ibopomofo=12583;e.ibreve=301;e.icaron=464;e.icircle=9432;e.icircumflex=238;e.icyrillic=1110;e.idblgrave=521;e.ideographearthcircle=12943;e.ideographfirecircle=12939;e.ideographicallianceparen=12863;e.ideographiccallparen=12858;e.ideographiccentrecircle=12965;e.ideographicclose=12294;e.ideographiccomma=12289;e.ideographiccommaleft=65380;e.ideographiccongratulationparen=12855;e.ideographiccorrectcircle=12963;e.ideographicearthparen=12847;e.ideographicenterpriseparen=12861;e.ideographicexcellentcircle=12957;e.ideographicfestivalparen=12864;e.ideographicfinancialcircle=12950;e.ideographicfinancialparen=12854;e.ideographicfireparen=12843;e.ideographichaveparen=12850;e.ideographichighcircle=12964;e.ideographiciterationmark=12293;e.ideographiclaborcircle=12952;e.ideographiclaborparen=12856;e.ideographicleftcircle=12967;e.ideographiclowcircle=12966;e.ideographicmedicinecircle=12969;e.ideographicmetalparen=12846;e.ideographicmoonparen=12842;e.ideographicnameparen=12852;e.ideographicperiod=12290;e.ideographicprintcircle=12958;e.ideographicreachparen=12867;e.ideographicrepresentparen=12857;e.ideographicresourceparen=12862;e.ideographicrightcircle=12968;e.ideographicsecretcircle=12953;e.ideographicselfparen=12866;e.ideographicsocietyparen=12851;e.ideographicspace=12288;e.ideographicspecialparen=12853;e.ideographicstockparen=12849;e.ideographicstudyparen=12859;e.ideographicsunparen=12848;e.ideographicsuperviseparen=12860;e.ideographicwaterparen=12844;e.ideographicwoodparen=12845;e.ideographiczero=12295;e.ideographmetalcircle=12942;e.ideographmooncircle=12938;e.ideographnamecircle=12948;e.ideographsuncircle=12944;e.ideographwatercircle=12940;e.ideographwoodcircle=12941;e.ideva=2311;e.idieresis=239;e.idieresisacute=7727;e.idieresiscyrillic=1253;e.idotbelow=7883;e.iebrevecyrillic=1239;e.iecyrillic=1077;e.ieungacirclekorean=12917;e.ieungaparenkorean=12821;e.ieungcirclekorean=12903;e.ieungkorean=12615;e.ieungparenkorean=12807;e.igrave=236;e.igujarati=2695;e.igurmukhi=2567;e.ihiragana=12356;e.ihookabove=7881;e.iibengali=2440;e.iicyrillic=1080;e.iideva=2312;e.iigujarati=2696;e.iigurmukhi=2568;e.iimatragurmukhi=2624;e.iinvertedbreve=523;e.iishortcyrillic=1081;e.iivowelsignbengali=2496;e.iivowelsigndeva=2368;e.iivowelsigngujarati=2752;e.ij=307;e.ikatakana=12452;e.ikatakanahalfwidth=65394;e.ikorean=12643;e.ilde=732;e.iluyhebrew=1452;e.imacron=299;e.imacroncyrillic=1251;e.imageorapproximatelyequal=8787;e.imatragurmukhi=2623;e.imonospace=65353;e.increment=8710;e.infinity=8734;e.iniarmenian=1387;e.integral=8747;e.integralbottom=8993;e.integralbt=8993;e.integralex=63733;e.integraltop=8992;e.integraltp=8992;e.intersection=8745;e.intisquare=13061;e.invbullet=9688;e.invcircle=9689;e.invsmileface=9787;e.iocyrillic=1105;e.iogonek=303;e.iota=953;e.iotadieresis=970;e.iotadieresistonos=912;e.iotalatin=617;e.iotatonos=943;e.iparen=9380;e.irigurmukhi=2674;e.ismallhiragana=12355;e.ismallkatakana=12451;e.ismallkatakanahalfwidth=65384;e.issharbengali=2554;e.istroke=616;e.isuperior=63213;e.iterationhiragana=12445;e.iterationkatakana=12541;e.itilde=297;e.itildebelow=7725;e.iubopomofo=12585;e.iucyrillic=1102;e.ivowelsignbengali=2495;e.ivowelsigndeva=2367;e.ivowelsigngujarati=2751;e.izhitsacyrillic=1141;e.izhitsadblgravecyrillic=1143;e.j=106;e.jaarmenian=1393;e.jabengali=2460;e.jadeva=2332;e.jagujarati=2716;e.jagurmukhi=2588;e.jbopomofo=12560;e.jcaron=496;e.jcircle=9433;e.jcircumflex=309;e.jcrossedtail=669;e.jdotlessstroke=607;e.jecyrillic=1112;e.jeemarabic=1580;e.jeemfinalarabic=65182;e.jeeminitialarabic=65183;e.jeemmedialarabic=65184;e.jeharabic=1688;e.jehfinalarabic=64395;e.jhabengali=2461;e.jhadeva=2333;e.jhagujarati=2717;e.jhagurmukhi=2589;e.jheharmenian=1403;e.jis=12292;e.jmonospace=65354;e.jparen=9381;e.jsuperior=690;e.k=107;e.kabashkircyrillic=1185;e.kabengali=2453;e.kacute=7729;e.kacyrillic=1082;e.kadescendercyrillic=1179;e.kadeva=2325;e.kaf=1499;e.kafarabic=1603;e.kafdagesh=64315;e.kafdageshhebrew=64315;e.kaffinalarabic=65242;e.kafhebrew=1499;e.kafinitialarabic=65243;e.kafmedialarabic=65244;e.kafrafehebrew=64333;e.kagujarati=2709;e.kagurmukhi=2581;e.kahiragana=12363;e.kahookcyrillic=1220;e.kakatakana=12459;e.kakatakanahalfwidth=65398;e.kappa=954;e.kappasymbolgreek=1008;e.kapyeounmieumkorean=12657;e.kapyeounphieuphkorean=12676;e.kapyeounpieupkorean=12664;e.kapyeounssangpieupkorean=12665;e.karoriisquare=13069;e.kashidaautoarabic=1600;e.kashidaautonosidebearingarabic=1600;e.kasmallkatakana=12533;e.kasquare=13188;e.kasraarabic=1616;e.kasratanarabic=1613;e.kastrokecyrillic=1183;e.katahiraprolongmarkhalfwidth=65392;e.kaverticalstrokecyrillic=1181;e.kbopomofo=12558;e.kcalsquare=13193;e.kcaron=489;e.kcedilla=311;e.kcircle=9434;e.kcommaaccent=311;e.kdotbelow=7731;e.keharmenian=1412;e.kehiragana=12369;e.kekatakana=12465;e.kekatakanahalfwidth=65401;e.kenarmenian=1391;e.kesmallkatakana=12534;e.kgreenlandic=312;e.khabengali=2454;e.khacyrillic=1093;e.khadeva=2326;e.khagujarati=2710;e.khagurmukhi=2582;e.khaharabic=1582;e.khahfinalarabic=65190;e.khahinitialarabic=65191;e.khahmedialarabic=65192;e.kheicoptic=999;e.khhadeva=2393;e.khhagurmukhi=2649;e.khieukhacirclekorean=12920;e.khieukhaparenkorean=12824;e.khieukhcirclekorean=12906;e.khieukhkorean=12619;e.khieukhparenkorean=12810;e.khokhaithai=3586;e.khokhonthai=3589;e.khokhuatthai=3587;e.khokhwaithai=3588;e.khomutthai=3675;e.khook=409;e.khorakhangthai=3590;e.khzsquare=13201;e.kihiragana=12365;e.kikatakana=12461;e.kikatakanahalfwidth=65399;e.kiroguramusquare=13077;e.kiromeetorusquare=13078;e.kirosquare=13076;e.kiyeokacirclekorean=12910;e.kiyeokaparenkorean=12814;e.kiyeokcirclekorean=12896;e.kiyeokkorean=12593;e.kiyeokparenkorean=12800;e.kiyeoksioskorean=12595;e.kjecyrillic=1116;e.klinebelow=7733;e.klsquare=13208;e.kmcubedsquare=13222;e.kmonospace=65355;e.kmsquaredsquare=13218;e.kohiragana=12371;e.kohmsquare=13248;e.kokaithai=3585;e.kokatakana=12467;e.kokatakanahalfwidth=65402;e.kooposquare=13086;e.koppacyrillic=1153;e.koreanstandardsymbol=12927;e.koroniscmb=835;e.kparen=9382;e.kpasquare=13226;e.ksicyrillic=1135;e.ktsquare=13263;e.kturned=670;e.kuhiragana=12367;e.kukatakana=12463;e.kukatakanahalfwidth=65400;e.kvsquare=13240;e.kwsquare=13246;e.l=108;e.labengali=2482;e.lacute=314;e.ladeva=2354;e.lagujarati=2738;e.lagurmukhi=2610;e.lakkhangyaothai=3653;e.lamaleffinalarabic=65276;e.lamalefhamzaabovefinalarabic=65272;e.lamalefhamzaaboveisolatedarabic=65271;e.lamalefhamzabelowfinalarabic=65274;e.lamalefhamzabelowisolatedarabic=65273;e.lamalefisolatedarabic=65275;e.lamalefmaddaabovefinalarabic=65270;e.lamalefmaddaaboveisolatedarabic=65269;e.lamarabic=1604;e.lambda=955;e.lambdastroke=411;e.lamed=1500;e.lameddagesh=64316;e.lameddageshhebrew=64316;e.lamedhebrew=1500;e.lamfinalarabic=65246;e.lamhahinitialarabic=64714;e.laminitialarabic=65247;e.lamjeeminitialarabic=64713;e.lamkhahinitialarabic=64715;e.lamlamhehisolatedarabic=65010;e.lammedialarabic=65248;e.lammeemhahinitialarabic=64904;e.lammeeminitialarabic=64716;e.largecircle=9711;e.lbar=410;e.lbelt=620;e.lbopomofo=12556;e.lcaron=318;e.lcedilla=316;e.lcircle=9435;e.lcircumflexbelow=7741;e.lcommaaccent=316;e.ldot=320;e.ldotaccent=320;e.ldotbelow=7735;e.ldotbelowmacron=7737;e.leftangleabovecmb=794;e.lefttackbelowcmb=792;e.less=60;e.lessequal=8804;e.lessequalorgreater=8922;e.lessmonospace=65308;e.lessorequivalent=8818;e.lessorgreater=8822;e.lessoverequal=8806;e.lesssmall=65124;e.lezh=622;e.lfblock=9612;e.lhookretroflex=621;e.lira=8356;e.liwnarmenian=1388;e.lj=457;e.ljecyrillic=1113;e.ll=63168;e.lladeva=2355;e.llagujarati=2739;e.llinebelow=7739;e.llladeva=2356;e.llvocalicbengali=2529;e.llvocalicdeva=2401;e.llvocalicvowelsignbengali=2531;e.llvocalicvowelsigndeva=2403;e.lmiddletilde=619;e.lmonospace=65356;e.lmsquare=13264;e.lochulathai=3628;e.logicaland=8743;e.logicalnot=172;e.logicalnotreversed=8976;e.logicalor=8744;e.lolingthai=3621;e.longs=383;e.lowlinecenterline=65102;e.lowlinecmb=818;e.lowlinedashed=65101;e.lozenge=9674;e.lparen=9383;e.lslash=322;e.lsquare=8467;e.lsuperior=63214;e.ltshade=9617;e.luthai=3622;e.lvocalicbengali=2444;e.lvocalicdeva=2316;e.lvocalicvowelsignbengali=2530;e.lvocalicvowelsigndeva=2402;e.lxsquare=13267;e.m=109;e.mabengali=2478;e.macron=175;e.macronbelowcmb=817;e.macroncmb=772;e.macronlowmod=717;e.macronmonospace=65507;e.macute=7743;e.madeva=2350;e.magujarati=2734;e.magurmukhi=2606;e.mahapakhhebrew=1444;e.mahapakhlefthebrew=1444;e.mahiragana=12414;e.maichattawalowleftthai=63637;e.maichattawalowrightthai=63636;e.maichattawathai=3659;e.maichattawaupperleftthai=63635;e.maieklowleftthai=63628;e.maieklowrightthai=63627;e.maiekthai=3656;e.maiekupperleftthai=63626;e.maihanakatleftthai=63620;e.maihanakatthai=3633;e.maitaikhuleftthai=63625;e.maitaikhuthai=3655;e.maitholowleftthai=63631;e.maitholowrightthai=63630;e.maithothai=3657;e.maithoupperleftthai=63629;e.maitrilowleftthai=63634;e.maitrilowrightthai=63633;e.maitrithai=3658;e.maitriupperleftthai=63632;e.maiyamokthai=3654;e.makatakana=12510;e.makatakanahalfwidth=65423;e.male=9794;e.mansyonsquare=13127;e.maqafhebrew=1470;e.mars=9794;e.masoracirclehebrew=1455;e.masquare=13187;e.mbopomofo=12551;e.mbsquare=13268;e.mcircle=9436;e.mcubedsquare=13221;e.mdotaccent=7745;e.mdotbelow=7747;e.meemarabic=1605;e.meemfinalarabic=65250;e.meeminitialarabic=65251;e.meemmedialarabic=65252;e.meemmeeminitialarabic=64721;e.meemmeemisolatedarabic=64584;e.meetorusquare=13133;e.mehiragana=12417;e.meizierasquare=13182;e.mekatakana=12513;e.mekatakanahalfwidth=65426;e.mem=1502;e.memdagesh=64318;e.memdageshhebrew=64318;e.memhebrew=1502;e.menarmenian=1396;e.merkhahebrew=1445;e.merkhakefulahebrew=1446;e.merkhakefulalefthebrew=1446;e.merkhalefthebrew=1445;e.mhook=625;e.mhzsquare=13202;e.middledotkatakanahalfwidth=65381;e.middot=183;e.mieumacirclekorean=12914;e.mieumaparenkorean=12818;e.mieumcirclekorean=12900;e.mieumkorean=12609;e.mieumpansioskorean=12656;e.mieumparenkorean=12804;e.mieumpieupkorean=12654;e.mieumsioskorean=12655;e.mihiragana=12415;e.mikatakana=12511;e.mikatakanahalfwidth=65424;e.minus=8722;e.minusbelowcmb=800;e.minuscircle=8854;e.minusmod=727;e.minusplus=8723;e.minute=8242;e.miribaarusquare=13130;e.mirisquare=13129 +;e.mlonglegturned=624;e.mlsquare=13206;e.mmcubedsquare=13219;e.mmonospace=65357;e.mmsquaredsquare=13215;e.mohiragana=12418;e.mohmsquare=13249;e.mokatakana=12514;e.mokatakanahalfwidth=65427;e.molsquare=13270;e.momathai=3617;e.moverssquare=13223;e.moverssquaredsquare=13224;e.mparen=9384;e.mpasquare=13227;e.mssquare=13235;e.msuperior=63215;e.mturned=623;e.mu=181;e.mu1=181;e.muasquare=13186;e.muchgreater=8811;e.muchless=8810;e.mufsquare=13196;e.mugreek=956;e.mugsquare=13197;e.muhiragana=12416;e.mukatakana=12512;e.mukatakanahalfwidth=65425;e.mulsquare=13205;e.multiply=215;e.mumsquare=13211;e.munahhebrew=1443;e.munahlefthebrew=1443;e.musicalnote=9834;e.musicalnotedbl=9835;e.musicflatsign=9837;e.musicsharpsign=9839;e.mussquare=13234;e.muvsquare=13238;e.muwsquare=13244;e.mvmegasquare=13241;e.mvsquare=13239;e.mwmegasquare=13247;e.mwsquare=13245;e.n=110;e.nabengali=2472;e.nabla=8711;e.nacute=324;e.nadeva=2344;e.nagujarati=2728;e.nagurmukhi=2600;e.nahiragana=12394;e.nakatakana=12490;e.nakatakanahalfwidth=65413;e.napostrophe=329;e.nasquare=13185;e.nbopomofo=12555;e.nbspace=160;e.ncaron=328;e.ncedilla=326;e.ncircle=9437;e.ncircumflexbelow=7755;e.ncommaaccent=326;e.ndotaccent=7749;e.ndotbelow=7751;e.nehiragana=12397;e.nekatakana=12493;e.nekatakanahalfwidth=65416;e.newsheqelsign=8362;e.nfsquare=13195;e.ngabengali=2457;e.ngadeva=2329;e.ngagujarati=2713;e.ngagurmukhi=2585;e.ngonguthai=3591;e.nhiragana=12435;e.nhookleft=626;e.nhookretroflex=627;e.nieunacirclekorean=12911;e.nieunaparenkorean=12815;e.nieuncieuckorean=12597;e.nieuncirclekorean=12897;e.nieunhieuhkorean=12598;e.nieunkorean=12596;e.nieunpansioskorean=12648;e.nieunparenkorean=12801;e.nieunsioskorean=12647;e.nieuntikeutkorean=12646;e.nihiragana=12395;e.nikatakana=12491;e.nikatakanahalfwidth=65414;e.nikhahitleftthai=63641;e.nikhahitthai=3661;e.nine=57;e.ninearabic=1641;e.ninebengali=2543;e.ninecircle=9320;e.ninecircleinversesansserif=10130;e.ninedeva=2415;e.ninegujarati=2799;e.ninegurmukhi=2671;e.ninehackarabic=1641;e.ninehangzhou=12329;e.nineideographicparen=12840;e.nineinferior=8329;e.ninemonospace=65305;e.nineoldstyle=63289;e.nineparen=9340;e.nineperiod=9360;e.ninepersian=1785;e.nineroman=8568;e.ninesuperior=8313;e.nineteencircle=9330;e.nineteenparen=9350;e.nineteenperiod=9370;e.ninethai=3673;e.nj=460;e.njecyrillic=1114;e.nkatakana=12531;e.nkatakanahalfwidth=65437;e.nlegrightlong=414;e.nlinebelow=7753;e.nmonospace=65358;e.nmsquare=13210;e.nnabengali=2467;e.nnadeva=2339;e.nnagujarati=2723;e.nnagurmukhi=2595;e.nnnadeva=2345;e.nohiragana=12398;e.nokatakana=12494;e.nokatakanahalfwidth=65417;e.nonbreakingspace=160;e.nonenthai=3603;e.nonuthai=3609;e.noonarabic=1606;e.noonfinalarabic=65254;e.noonghunnaarabic=1722;e.noonghunnafinalarabic=64415;e.nooninitialarabic=65255;e.noonjeeminitialarabic=64722;e.noonjeemisolatedarabic=64587;e.noonmedialarabic=65256;e.noonmeeminitialarabic=64725;e.noonmeemisolatedarabic=64590;e.noonnoonfinalarabic=64653;e.notcontains=8716;e.notelement=8713;e.notelementof=8713;e.notequal=8800;e.notgreater=8815;e.notgreaternorequal=8817;e.notgreaternorless=8825;e.notidentical=8802;e.notless=8814;e.notlessnorequal=8816;e.notparallel=8742;e.notprecedes=8832;e.notsubset=8836;e.notsucceeds=8833;e.notsuperset=8837;e.nowarmenian=1398;e.nparen=9385;e.nssquare=13233;e.nsuperior=8319;e.ntilde=241;e.nu=957;e.nuhiragana=12396;e.nukatakana=12492;e.nukatakanahalfwidth=65415;e.nuktabengali=2492;e.nuktadeva=2364;e.nuktagujarati=2748;e.nuktagurmukhi=2620;e.numbersign=35;e.numbersignmonospace=65283;e.numbersignsmall=65119;e.numeralsigngreek=884;e.numeralsignlowergreek=885;e.numero=8470;e.nun=1504;e.nundagesh=64320;e.nundageshhebrew=64320;e.nunhebrew=1504;e.nvsquare=13237;e.nwsquare=13243;e.nyabengali=2462;e.nyadeva=2334;e.nyagujarati=2718;e.nyagurmukhi=2590;e.o=111;e.oacute=243;e.oangthai=3629;e.obarred=629;e.obarredcyrillic=1257;e.obarreddieresiscyrillic=1259;e.obengali=2451;e.obopomofo=12571;e.obreve=335;e.ocandradeva=2321;e.ocandragujarati=2705;e.ocandravowelsigndeva=2377;e.ocandravowelsigngujarati=2761;e.ocaron=466;e.ocircle=9438;e.ocircumflex=244;e.ocircumflexacute=7889;e.ocircumflexdotbelow=7897;e.ocircumflexgrave=7891;e.ocircumflexhookabove=7893;e.ocircumflextilde=7895;e.ocyrillic=1086;e.odblacute=337;e.odblgrave=525;e.odeva=2323;e.odieresis=246;e.odieresiscyrillic=1255;e.odotbelow=7885;e.oe=339;e.oekorean=12634;e.ogonek=731;e.ogonekcmb=808;e.ograve=242;e.ogujarati=2707;e.oharmenian=1413;e.ohiragana=12362;e.ohookabove=7887;e.ohorn=417;e.ohornacute=7899;e.ohorndotbelow=7907;e.ohorngrave=7901;e.ohornhookabove=7903;e.ohorntilde=7905;e.ohungarumlaut=337;e.oi=419;e.oinvertedbreve=527;e.okatakana=12458;e.okatakanahalfwidth=65397;e.okorean=12631;e.olehebrew=1451;e.omacron=333;e.omacronacute=7763;e.omacrongrave=7761;e.omdeva=2384;e.omega=969;e.omega1=982;e.omegacyrillic=1121;e.omegalatinclosed=631;e.omegaroundcyrillic=1147;e.omegatitlocyrillic=1149;e.omegatonos=974;e.omgujarati=2768;e.omicron=959;e.omicrontonos=972;e.omonospace=65359;e.one=49;e.onearabic=1633;e.onebengali=2535;e.onecircle=9312;e.onecircleinversesansserif=10122;e.onedeva=2407;e.onedotenleader=8228;e.oneeighth=8539;e.onefitted=63196;e.onegujarati=2791;e.onegurmukhi=2663;e.onehackarabic=1633;e.onehalf=189;e.onehangzhou=12321;e.oneideographicparen=12832;e.oneinferior=8321;e.onemonospace=65297;e.onenumeratorbengali=2548;e.oneoldstyle=63281;e.oneparen=9332;e.oneperiod=9352;e.onepersian=1777;e.onequarter=188;e.oneroman=8560;e.onesuperior=185;e.onethai=3665;e.onethird=8531;e.oogonek=491;e.oogonekmacron=493;e.oogurmukhi=2579;e.oomatragurmukhi=2635;e.oopen=596;e.oparen=9386;e.openbullet=9702;e.option=8997;e.ordfeminine=170;e.ordmasculine=186;e.orthogonal=8735;e.oshortdeva=2322;e.oshortvowelsigndeva=2378;e.oslash=248;e.oslashacute=511;e.osmallhiragana=12361;e.osmallkatakana=12457;e.osmallkatakanahalfwidth=65387;e.ostrokeacute=511;e.osuperior=63216;e.otcyrillic=1151;e.otilde=245;e.otildeacute=7757;e.otildedieresis=7759;e.oubopomofo=12577;e.overline=8254;e.overlinecenterline=65098;e.overlinecmb=773;e.overlinedashed=65097;e.overlinedblwavy=65100;e.overlinewavy=65099;e.overscore=175;e.ovowelsignbengali=2507;e.ovowelsigndeva=2379;e.ovowelsigngujarati=2763;e.p=112;e.paampssquare=13184;e.paasentosquare=13099;e.pabengali=2474;e.pacute=7765;e.padeva=2346;e.pagedown=8671;e.pageup=8670;e.pagujarati=2730;e.pagurmukhi=2602;e.pahiragana=12401;e.paiyannoithai=3631;e.pakatakana=12497;e.palatalizationcyrilliccmb=1156;e.palochkacyrillic=1216;e.pansioskorean=12671;e.paragraph=182;e.parallel=8741;e.parenleft=40;e.parenleftaltonearabic=64830;e.parenleftbt=63725;e.parenleftex=63724;e.parenleftinferior=8333;e.parenleftmonospace=65288;e.parenleftsmall=65113;e.parenleftsuperior=8317;e.parenlefttp=63723;e.parenleftvertical=65077;e.parenright=41;e.parenrightaltonearabic=64831;e.parenrightbt=63736;e.parenrightex=63735;e.parenrightinferior=8334;e.parenrightmonospace=65289;e.parenrightsmall=65114;e.parenrightsuperior=8318;e.parenrighttp=63734;e.parenrightvertical=65078;e.partialdiff=8706;e.paseqhebrew=1472;e.pashtahebrew=1433;e.pasquare=13225;e.patah=1463;e.patah11=1463;e.patah1d=1463;e.patah2a=1463;e.patahhebrew=1463;e.patahnarrowhebrew=1463;e.patahquarterhebrew=1463;e.patahwidehebrew=1463;e.pazerhebrew=1441;e.pbopomofo=12550;e.pcircle=9439;e.pdotaccent=7767;e.pe=1508;e.pecyrillic=1087;e.pedagesh=64324;e.pedageshhebrew=64324;e.peezisquare=13115;e.pefinaldageshhebrew=64323;e.peharabic=1662;e.peharmenian=1402;e.pehebrew=1508;e.pehfinalarabic=64343;e.pehinitialarabic=64344;e.pehiragana=12410;e.pehmedialarabic=64345;e.pekatakana=12506;e.pemiddlehookcyrillic=1191;e.perafehebrew=64334;e.percent=37;e.percentarabic=1642;e.percentmonospace=65285;e.percentsmall=65130;e.period=46;e.periodarmenian=1417;e.periodcentered=183;e.periodhalfwidth=65377;e.periodinferior=63207;e.periodmonospace=65294;e.periodsmall=65106;e.periodsuperior=63208;e.perispomenigreekcmb=834;e.perpendicular=8869;e.perthousand=8240;e.peseta=8359;e.pfsquare=13194;e.phabengali=2475;e.phadeva=2347;e.phagujarati=2731;e.phagurmukhi=2603;e.phi=966;e.phi1=981;e.phieuphacirclekorean=12922;e.phieuphaparenkorean=12826;e.phieuphcirclekorean=12908;e.phieuphkorean=12621;e.phieuphparenkorean=12812;e.philatin=632;e.phinthuthai=3642;e.phisymbolgreek=981;e.phook=421;e.phophanthai=3614;e.phophungthai=3612;e.phosamphaothai=3616;e.pi=960;e.pieupacirclekorean=12915;e.pieupaparenkorean=12819;e.pieupcieuckorean=12662;e.pieupcirclekorean=12901;e.pieupkiyeokkorean=12658;e.pieupkorean=12610;e.pieupparenkorean=12805;e.pieupsioskiyeokkorean=12660;e.pieupsioskorean=12612;e.pieupsiostikeutkorean=12661;e.pieupthieuthkorean=12663;e.pieuptikeutkorean=12659;e.pihiragana=12404;e.pikatakana=12500;e.pisymbolgreek=982;e.piwrarmenian=1411;e.plus=43;e.plusbelowcmb=799;e.pluscircle=8853;e.plusminus=177;e.plusmod=726;e.plusmonospace=65291;e.plussmall=65122;e.plussuperior=8314;e.pmonospace=65360;e.pmsquare=13272;e.pohiragana=12413;e.pointingindexdownwhite=9759;e.pointingindexleftwhite=9756;e.pointingindexrightwhite=9758;e.pointingindexupwhite=9757;e.pokatakana=12509;e.poplathai=3611;e.postalmark=12306;e.postalmarkface=12320;e.pparen=9387;e.precedes=8826;e.prescription=8478;e.primemod=697;e.primereversed=8245;e.product=8719;e.projective=8965;e.prolongedkana=12540;e.propellor=8984;e.propersubset=8834;e.propersuperset=8835;e.proportion=8759;e.proportional=8733;e.psi=968;e.psicyrillic=1137;e.psilipneumatacyrilliccmb=1158;e.pssquare=13232;e.puhiragana=12407;e.pukatakana=12503;e.pvsquare=13236;e.pwsquare=13242;e.q=113;e.qadeva=2392;e.qadmahebrew=1448;e.qafarabic=1602;e.qaffinalarabic=65238;e.qafinitialarabic=65239;e.qafmedialarabic=65240;e.qamats=1464;e.qamats10=1464;e.qamats1a=1464;e.qamats1c=1464;e.qamats27=1464;e.qamats29=1464;e.qamats33=1464;e.qamatsde=1464;e.qamatshebrew=1464;e.qamatsnarrowhebrew=1464;e.qamatsqatanhebrew=1464;e.qamatsqatannarrowhebrew=1464;e.qamatsqatanquarterhebrew=1464;e.qamatsqatanwidehebrew=1464;e.qamatsquarterhebrew=1464;e.qamatswidehebrew=1464;e.qarneyparahebrew=1439;e.qbopomofo=12561;e.qcircle=9440;e.qhook=672;e.qmonospace=65361;e.qof=1511;e.qofdagesh=64327;e.qofdageshhebrew=64327;e.qofhebrew=1511;e.qparen=9388;e.quarternote=9833;e.qubuts=1467;e.qubuts18=1467;e.qubuts25=1467;e.qubuts31=1467;e.qubutshebrew=1467;e.qubutsnarrowhebrew=1467;e.qubutsquarterhebrew=1467;e.qubutswidehebrew=1467;e.question=63;e.questionarabic=1567;e.questionarmenian=1374;e.questiondown=191;e.questiondownsmall=63423;e.questiongreek=894;e.questionmonospace=65311;e.questionsmall=63295;e.quotedbl=34;e.quotedblbase=8222;e.quotedblleft=8220;e.quotedblmonospace=65282;e.quotedblprime=12318;e.quotedblprimereversed=12317;e.quotedblright=8221;e.quoteleft=8216;e.quoteleftreversed=8219;e.quotereversed=8219;e.quoteright=8217;e.quoterightn=329;e.quotesinglbase=8218;e.quotesingle=39;e.quotesinglemonospace=65287;e.r=114;e.raarmenian=1404;e.rabengali=2480;e.racute=341;e.radeva=2352;e.radical=8730;e.radicalex=63717;e.radoverssquare=13230;e.radoverssquaredsquare=13231;e.radsquare=13229;e.rafe=1471;e.rafehebrew=1471;e.ragujarati=2736;e.ragurmukhi=2608;e.rahiragana=12425;e.rakatakana=12521;e.rakatakanahalfwidth=65431;e.ralowerdiagonalbengali=2545;e.ramiddlediagonalbengali=2544;e.ramshorn=612;e.ratio=8758;e.rbopomofo=12566;e.rcaron=345;e.rcedilla=343;e.rcircle=9441;e.rcommaaccent=343;e.rdblgrave=529;e.rdotaccent=7769;e.rdotbelow=7771;e.rdotbelowmacron=7773;e.referencemark=8251;e.reflexsubset=8838;e.reflexsuperset=8839;e.registered=174;e.registersans=63720;e.registerserif=63194;e.reharabic=1585;e.reharmenian=1408;e.rehfinalarabic=65198;e.rehiragana=12428;e.rekatakana=12524;e.rekatakanahalfwidth=65434;e.resh=1512;e.reshdageshhebrew=64328;e.reshhebrew=1512;e.reversedtilde=8765;e.reviahebrew=1431;e.reviamugrashhebrew=1431;e.revlogicalnot=8976;e.rfishhook=638;e.rfishhookreversed=639;e.rhabengali=2525;e.rhadeva=2397;e.rho=961;e.rhook=637;e.rhookturned=635;e.rhookturnedsuperior=693;e.rhosymbolgreek=1009;e.rhotichookmod=734;e.rieulacirclekorean=12913;e.rieulaparenkorean=12817;e.rieulcirclekorean=12899;e.rieulhieuhkorean=12608;e.rieulkiyeokkorean=12602;e.rieulkiyeoksioskorean=12649;e.rieulkorean=12601;e.rieulmieumkorean=12603;e.rieulpansioskorean=12652;e.rieulparenkorean=12803;e.rieulphieuphkorean=12607;e.rieulpieupkorean=12604;e.rieulpieupsioskorean=12651;e.rieulsioskorean=12605;e.rieulthieuthkorean=12606;e.rieultikeutkorean=12650;e.rieulyeorinhieuhkorean=12653;e.rightangle=8735;e.righttackbelowcmb=793;e.righttriangle=8895;e.rihiragana=12426;e.rikatakana=12522;e.rikatakanahalfwidth=65432;e.ring=730;e.ringbelowcmb=805;e.ringcmb=778;e.ringhalfleft=703;e.ringhalfleftarmenian=1369;e.ringhalfleftbelowcmb=796;e.ringhalfleftcentered=723;e.ringhalfright=702;e.ringhalfrightbelowcmb=825;e.ringhalfrightcentered=722;e.rinvertedbreve=531;e.rittorusquare=13137;e.rlinebelow=7775;e.rlongleg=636;e.rlonglegturned=634;e.rmonospace=65362;e.rohiragana=12429;e.rokatakana=12525;e.rokatakanahalfwidth=65435;e.roruathai=3619;e.rparen=9389;e.rrabengali=2524;e.rradeva=2353;e.rragurmukhi=2652;e.rreharabic=1681;e.rrehfinalarabic=64397;e.rrvocalicbengali=2528;e.rrvocalicdeva=2400;e.rrvocalicgujarati=2784;e.rrvocalicvowelsignbengali=2500;e.rrvocalicvowelsigndeva=2372;e.rrvocalicvowelsigngujarati=2756;e.rsuperior=63217;e.rtblock=9616;e.rturned=633;e.rturnedsuperior=692;e.ruhiragana=12427;e.rukatakana=12523;e.rukatakanahalfwidth=65433;e.rupeemarkbengali=2546;e.rupeesignbengali=2547;e.rupiah=63197;e.ruthai=3620;e.rvocalicbengali=2443;e.rvocalicdeva=2315;e.rvocalicgujarati=2699;e.rvocalicvowelsignbengali=2499;e.rvocalicvowelsigndeva=2371;e.rvocalicvowelsigngujarati=2755;e.s=115;e.sabengali=2488;e.sacute=347;e.sacutedotaccent=7781;e.sadarabic=1589;e.sadeva=2360;e.sadfinalarabic=65210;e.sadinitialarabic=65211;e.sadmedialarabic=65212;e.sagujarati=2744;e.sagurmukhi=2616;e.sahiragana=12373;e.sakatakana=12469;e.sakatakanahalfwidth=65403;e.sallallahoualayhewasallamarabic=65018;e.samekh=1505;e.samekhdagesh=64321;e.samekhdageshhebrew=64321;e.samekhhebrew=1505;e.saraaathai=3634;e.saraaethai=3649;e.saraaimaimalaithai=3652;e.saraaimaimuanthai=3651;e.saraamthai=3635;e.saraathai=3632;e.saraethai=3648;e.saraiileftthai=63622;e.saraiithai=3637;e.saraileftthai=63621;e.saraithai=3636;e.saraothai=3650;e.saraueeleftthai=63624;e.saraueethai=3639;e.saraueleftthai=63623;e.sarauethai=3638;e.sarauthai=3640;e.sarauuthai=3641;e.sbopomofo=12569;e.scaron=353;e.scarondotaccent=7783;e.scedilla=351;e.schwa=601;e.schwacyrillic=1241;e.schwadieresiscyrillic=1243;e.schwahook=602;e.scircle=9442;e.scircumflex=349;e.scommaaccent=537;e.sdotaccent=7777;e.sdotbelow=7779;e.sdotbelowdotaccent=7785;e.seagullbelowcmb=828;e.second=8243;e.secondtonechinese=714;e.section=167;e.seenarabic=1587;e.seenfinalarabic=65202;e.seeninitialarabic=65203;e.seenmedialarabic=65204;e.segol=1462;e.segol13=1462;e.segol1f=1462;e.segol2c=1462;e.segolhebrew=1462;e.segolnarrowhebrew=1462;e.segolquarterhebrew=1462;e.segoltahebrew=1426;e.segolwidehebrew=1462;e.seharmenian=1405;e.sehiragana=12379;e.sekatakana=12475;e.sekatakanahalfwidth=65406;e.semicolon=59;e.semicolonarabic=1563;e.semicolonmonospace=65307;e.semicolonsmall=65108;e.semivoicedmarkkana=12444;e.semivoicedmarkkanahalfwidth=65439;e.sentisquare=13090;e.sentosquare=13091;e.seven=55;e.sevenarabic=1639;e.sevenbengali=2541;e.sevencircle=9318;e.sevencircleinversesansserif=10128;e.sevendeva=2413;e.seveneighths=8542;e.sevengujarati=2797;e.sevengurmukhi=2669;e.sevenhackarabic=1639;e.sevenhangzhou=12327;e.sevenideographicparen=12838;e.seveninferior=8327;e.sevenmonospace=65303;e.sevenoldstyle=63287;e.sevenparen=9338;e.sevenperiod=9358;e.sevenpersian=1783;e.sevenroman=8566;e.sevensuperior=8311;e.seventeencircle=9328;e.seventeenparen=9348;e.seventeenperiod=9368;e.seventhai=3671;e.sfthyphen=173;e.shaarmenian=1399;e.shabengali=2486;e.shacyrillic=1096;e.shaddaarabic=1617;e.shaddadammaarabic=64609;e.shaddadammatanarabic=64606;e.shaddafathaarabic=64608;e.shaddakasraarabic=64610;e.shaddakasratanarabic=64607;e.shade=9618;e.shadedark=9619;e.shadelight=9617;e.shademedium=9618;e.shadeva=2358;e.shagujarati=2742;e.shagurmukhi=2614;e.shalshelethebrew=1427;e.shbopomofo=12565;e.shchacyrillic=1097;e.sheenarabic=1588;e.sheenfinalarabic=65206;e.sheeninitialarabic=65207;e.sheenmedialarabic=65208;e.sheicoptic=995;e.sheqel=8362;e.sheqelhebrew=8362;e.sheva=1456;e.sheva115=1456;e.sheva15=1456;e.sheva22=1456;e.sheva2e=1456;e.shevahebrew=1456;e.shevanarrowhebrew=1456;e.shevaquarterhebrew=1456;e.shevawidehebrew=1456;e.shhacyrillic=1211;e.shimacoptic=1005;e.shin=1513;e.shindagesh=64329;e.shindageshhebrew=64329;e.shindageshshindot=64300;e.shindageshshindothebrew=64300;e.shindageshsindot=64301;e.shindageshsindothebrew=64301;e.shindothebrew=1473;e.shinhebrew=1513;e.shinshindot=64298;e.shinshindothebrew=64298;e.shinsindot=64299;e.shinsindothebrew=64299;e.shook=642;e.sigma=963;e.sigma1=962;e.sigmafinal=962;e.sigmalunatesymbolgreek=1010;e.sihiragana=12375;e.sikatakana=12471;e.sikatakanahalfwidth=65404;e.siluqhebrew=1469;e.siluqlefthebrew=1469;e.similar=8764;e.sindothebrew=1474;e.siosacirclekorean=12916;e.siosaparenkorean=12820;e.sioscieuckorean=12670;e.sioscirclekorean=12902;e.sioskiyeokkorean=12666;e.sioskorean=12613;e.siosnieunkorean=12667;e.siosparenkorean=12806;e.siospieupkorean=12669;e.siostikeutkorean=12668;e.six=54;e.sixarabic=1638;e.sixbengali=2540;e.sixcircle=9317;e.sixcircleinversesansserif=10127;e.sixdeva=2412;e.sixgujarati=2796;e.sixgurmukhi=2668;e.sixhackarabic=1638;e.sixhangzhou=12326;e.sixideographicparen=12837;e.sixinferior=8326;e.sixmonospace=65302;e.sixoldstyle=63286;e.sixparen=9337;e.sixperiod=9357;e.sixpersian=1782;e.sixroman=8565;e.sixsuperior=8310;e.sixteencircle=9327;e.sixteencurrencydenominatorbengali=2553;e.sixteenparen=9347;e.sixteenperiod=9367;e.sixthai=3670;e.slash=47;e.slashmonospace=65295;e.slong=383;e.slongdotaccent=7835;e.smileface=9786;e.smonospace=65363;e.sofpasuqhebrew=1475;e.softhyphen=173;e.softsigncyrillic=1100;e.sohiragana=12381;e.sokatakana=12477;e.sokatakanahalfwidth=65407;e.soliduslongoverlaycmb=824;e.solidusshortoverlaycmb=823;e.sorusithai=3625;e.sosalathai=3624;e.sosothai=3595;e.sosuathai=3626;e.space=32;e.spacehackarabic=32;e.spade=9824;e.spadesuitblack=9824;e.spadesuitwhite=9828;e.sparen=9390;e.squarebelowcmb=827;e.squarecc=13252;e.squarecm=13213;e.squarediagonalcrosshatchfill=9641;e.squarehorizontalfill=9636;e.squarekg=13199;e.squarekm=13214;e.squarekmcapital=13262;e.squareln=13265;e.squarelog=13266;e.squaremg=13198;e.squaremil=13269;e.squaremm=13212;e.squaremsquared=13217;e.squareorthogonalcrosshatchfill=9638;e.squareupperlefttolowerrightfill=9639;e.squareupperrighttolowerleftfill=9640;e.squareverticalfill=9637;e.squarewhitewithsmallblack=9635;e.srsquare=13275;e.ssabengali=2487;e.ssadeva=2359;e.ssagujarati=2743;e.ssangcieuckorean=12617;e.ssanghieuhkorean=12677;e.ssangieungkorean=12672;e.ssangkiyeokkorean=12594;e.ssangnieunkorean=12645;e.ssangpieupkorean=12611;e.ssangsioskorean=12614;e.ssangtikeutkorean=12600;e.ssuperior=63218;e.sterling=163;e.sterlingmonospace=65505;e.strokelongoverlaycmb=822;e.strokeshortoverlaycmb=821;e.subset=8834;e.subsetnotequal=8842;e.subsetorequal=8838;e.succeeds=8827;e.suchthat=8715;e.suhiragana=12377;e.sukatakana=12473;e.sukatakanahalfwidth=65405;e.sukunarabic=1618;e.summation=8721;e.sun=9788;e.superset=8835;e.supersetnotequal=8843;e.supersetorequal=8839;e.svsquare=13276;e.syouwaerasquare=13180;e.t=116;e.tabengali=2468;e.tackdown=8868;e.tackleft=8867;e.tadeva=2340;e.tagujarati=2724;e.tagurmukhi=2596;e.taharabic=1591;e.tahfinalarabic=65218;e.tahinitialarabic=65219;e.tahiragana=12383;e.tahmedialarabic=65220;e.taisyouerasquare=13181;e.takatakana=12479;e.takatakanahalfwidth=65408;e.tatweelarabic=1600;e.tau=964;e.tav=1514;e.tavdages=64330;e.tavdagesh=64330;e.tavdageshhebrew=64330;e.tavhebrew=1514;e.tbar=359;e.tbopomofo=12554;e.tcaron=357;e.tccurl=680;e.tcedilla=355;e.tcheharabic=1670;e.tchehfinalarabic=64379;e.tchehinitialarabic=64380;e.tchehmedialarabic=64381;e.tcircle=9443;e.tcircumflexbelow=7793;e.tcommaaccent=355;e.tdieresis=7831;e.tdotaccent=7787;e.tdotbelow=7789;e.tecyrillic=1090;e.tedescendercyrillic=1197;e.teharabic=1578;e.tehfinalarabic=65174;e.tehhahinitialarabic=64674;e.tehhahisolatedarabic=64524;e.tehinitialarabic=65175;e.tehiragana=12390;e.tehjeeminitialarabic=64673;e.tehjeemisolatedarabic=64523;e.tehmarbutaarabic=1577;e.tehmarbutafinalarabic=65172;e.tehmedialarabic=65176;e.tehmeeminitialarabic=64676;e.tehmeemisolatedarabic=64526;e.tehnoonfinalarabic=64627;e.tekatakana=12486;e.tekatakanahalfwidth=65411;e.telephone=8481;e.telephoneblack=9742;e.telishagedolahebrew=1440;e.telishaqetanahebrew=1449;e.tencircle=9321;e.tenideographicparen=12841;e.tenparen=9341;e.tenperiod=9361;e.tenroman=8569;e.tesh=679;e.tet=1496;e.tetdagesh=64312;e.tetdageshhebrew=64312;e.tethebrew=1496;e.tetsecyrillic=1205;e.tevirhebrew=1435;e.tevirlefthebrew=1435;e.thabengali=2469;e.thadeva=2341;e.thagujarati=2725;e.thagurmukhi=2597;e.thalarabic=1584;e.thalfinalarabic=65196;e.thanthakhatlowleftthai=63640;e.thanthakhatlowrightthai=63639;e.thanthakhatthai=3660;e.thanthakhatupperleftthai=63638;e.theharabic=1579;e.thehfinalarabic=65178;e.thehinitialarabic=65179;e.thehmedialarabic=65180;e.thereexists=8707;e.therefore=8756;e.theta=952;e.theta1=977;e.thetasymbolgreek=977;e.thieuthacirclekorean=12921;e.thieuthaparenkorean=12825;e.thieuthcirclekorean=12907;e.thieuthkorean=12620;e.thieuthparenkorean=12811;e.thirteencircle=9324;e.thirteenparen=9344;e.thirteenperiod=9364;e.thonangmonthothai=3601;e.thook=429;e.thophuthaothai=3602;e.thorn=254;e.thothahanthai=3607;e.thothanthai=3600;e.thothongthai=3608;e.thothungthai=3606;e.thousandcyrillic=1154;e.thousandsseparatorarabic=1644;e.thousandsseparatorpersian=1644;e.three=51;e.threearabic=1635;e.threebengali=2537;e.threecircle=9314;e.threecircleinversesansserif=10124;e.threedeva=2409;e.threeeighths=8540;e.threegujarati=2793;e.threegurmukhi=2665;e.threehackarabic=1635;e.threehangzhou=12323;e.threeideographicparen=12834;e.threeinferior=8323;e.threemonospace=65299;e.threenumeratorbengali=2550;e.threeoldstyle=63283;e.threeparen=9334;e.threeperiod=9354;e.threepersian=1779;e.threequarters=190;e.threequartersemdash=63198;e.threeroman=8562;e.threesuperior=179;e.threethai=3667;e.thzsquare=13204;e.tihiragana=12385;e.tikatakana=12481;e.tikatakanahalfwidth=65409;e.tikeutacirclekorean=12912;e.tikeutaparenkorean=12816;e.tikeutcirclekorean=12898;e.tikeutkorean=12599;e.tikeutparenkorean=12802;e.tilde=732;e.tildebelowcmb=816;e.tildecmb=771;e.tildecomb=771;e.tildedoublecmb=864;e.tildeoperator=8764;e.tildeoverlaycmb=820;e.tildeverticalcmb=830;e.timescircle=8855;e.tipehahebrew=1430;e.tipehalefthebrew=1430;e.tippigurmukhi=2672;e.titlocyrilliccmb=1155;e.tiwnarmenian=1407;e.tlinebelow=7791;e.tmonospace=65364;e.toarmenian=1385;e.tohiragana=12392;e.tokatakana=12488;e.tokatakanahalfwidth=65412;e.tonebarextrahighmod=741;e.tonebarextralowmod=745;e.tonebarhighmod=742;e.tonebarlowmod=744;e.tonebarmidmod=743;e.tonefive=445;e.tonesix=389;e.tonetwo=424;e.tonos=900;e.tonsquare=13095;e.topatakthai=3599;e.tortoiseshellbracketleft=12308;e.tortoiseshellbracketleftsmall=65117;e.tortoiseshellbracketleftvertical=65081;e.tortoiseshellbracketright=12309;e.tortoiseshellbracketrightsmall=65118;e.tortoiseshellbracketrightvertical=65082;e.totaothai=3605;e.tpalatalhook=427;e.tparen=9391;e.trademark=8482;e.trademarksans=63722;e.trademarkserif=63195;e.tretroflexhook=648;e.triagdn=9660;e.triaglf=9668;e.triagrt=9658;e.triagup=9650;e.ts=678;e.tsadi=1510;e.tsadidagesh=64326;e.tsadidageshhebrew=64326;e.tsadihebrew=1510;e.tsecyrillic=1094;e.tsere=1461;e.tsere12=1461;e.tsere1e=1461;e.tsere2b=1461;e.tserehebrew=1461;e.tserenarrowhebrew=1461;e.tserequarterhebrew=1461;e.tserewidehebrew=1461;e.tshecyrillic=1115;e.tsuperior=63219;e.ttabengali=2463;e.ttadeva=2335;e.ttagujarati=2719;e.ttagurmukhi=2591;e.tteharabic=1657;e.ttehfinalarabic=64359;e.ttehinitialarabic=64360;e.ttehmedialarabic=64361;e.tthabengali=2464;e.tthadeva=2336;e.tthagujarati=2720;e.tthagurmukhi=2592;e.tturned=647;e.tuhiragana=12388;e.tukatakana=12484;e.tukatakanahalfwidth=65410;e.tusmallhiragana=12387;e.tusmallkatakana=12483;e.tusmallkatakanahalfwidth=65391;e.twelvecircle=9323;e.twelveparen=9343;e.twelveperiod=9363;e.twelveroman=8571;e.twentycircle=9331;e.twentyhangzhou=21316;e.twentyparen=9351;e.twentyperiod=9371;e.two=50;e.twoarabic=1634;e.twobengali=2536;e.twocircle=9313;e.twocircleinversesansserif=10123;e.twodeva=2408;e.twodotenleader=8229;e.twodotleader=8229;e.twodotleadervertical=65072;e.twogujarati=2792;e.twogurmukhi=2664;e.twohackarabic=1634;e.twohangzhou=12322;e.twoideographicparen=12833;e.twoinferior=8322;e.twomonospace=65298;e.twonumeratorbengali=2549;e.twooldstyle=63282;e.twoparen=9333;e.twoperiod=9353;e.twopersian=1778;e.tworoman=8561;e.twostroke=443;e.twosuperior=178;e.twothai=3666;e.twothirds=8532;e.u=117;e.uacute=250;e.ubar=649;e.ubengali=2441;e.ubopomofo=12584;e.ubreve=365;e.ucaron=468;e.ucircle=9444;e.ucircumflex=251;e.ucircumflexbelow=7799;e.ucyrillic=1091;e.udattadeva=2385;e.udblacute=369;e.udblgrave=533;e.udeva=2313;e.udieresis=252;e.udieresisacute=472;e.udieresisbelow=7795;e.udieresiscaron=474;e.udieresiscyrillic=1265;e.udieresisgrave=476;e.udieresismacron=470;e.udotbelow=7909;e.ugrave=249;e.ugujarati=2697;e.ugurmukhi=2569;e.uhiragana=12358;e.uhookabove=7911;e.uhorn=432;e.uhornacute=7913;e.uhorndotbelow=7921;e.uhorngrave=7915;e.uhornhookabove=7917;e.uhorntilde=7919;e.uhungarumlaut=369;e.uhungarumlautcyrillic=1267;e.uinvertedbreve=535;e.ukatakana=12454;e.ukatakanahalfwidth=65395;e.ukcyrillic=1145;e.ukorean=12636;e.umacron=363;e.umacroncyrillic=1263;e.umacrondieresis=7803;e.umatragurmukhi=2625;e.umonospace=65365;e.underscore=95;e.underscoredbl=8215;e.underscoremonospace=65343;e.underscorevertical=65075;e.underscorewavy=65103;e.union=8746;e.universal=8704;e.uogonek=371;e.uparen=9392;e.upblock=9600;e.upperdothebrew=1476;e.upsilon=965;e.upsilondieresis=971;e.upsilondieresistonos=944;e.upsilonlatin=650;e.upsilontonos=973;e.uptackbelowcmb=797;e.uptackmod=724;e.uragurmukhi=2675;e.uring=367;e.ushortcyrillic=1118;e.usmallhiragana=12357;e.usmallkatakana=12453;e.usmallkatakanahalfwidth=65385;e.ustraightcyrillic=1199;e.ustraightstrokecyrillic=1201;e.utilde=361;e.utildeacute=7801;e.utildebelow=7797;e.uubengali=2442;e.uudeva=2314;e.uugujarati=2698;e.uugurmukhi=2570;e.uumatragurmukhi=2626;e.uuvowelsignbengali=2498;e.uuvowelsigndeva=2370;e.uuvowelsigngujarati=2754;e.uvowelsignbengali=2497;e.uvowelsigndeva=2369;e.uvowelsigngujarati=2753;e.v=118;e.vadeva=2357;e.vagujarati=2741;e.vagurmukhi=2613;e.vakatakana=12535;e.vav=1493;e.vavdagesh=64309;e.vavdagesh65=64309;e.vavdageshhebrew=64309;e.vavhebrew=1493;e.vavholam=64331;e.vavholamhebrew=64331;e.vavvavhebrew=1520;e.vavyodhebrew=1521;e.vcircle=9445;e.vdotbelow=7807;e.vecyrillic=1074;e.veharabic=1700;e.vehfinalarabic=64363;e.vehinitialarabic=64364;e.vehmedialarabic=64365;e.vekatakana=12537;e.venus=9792;e.verticalbar=124;e.verticallineabovecmb=781;e.verticallinebelowcmb=809;e.verticallinelowmod=716;e.verticallinemod=712;e.vewarmenian=1406;e.vhook=651;e.vikatakana=12536;e.viramabengali=2509;e.viramadeva=2381;e.viramagujarati=2765;e.visargabengali=2435;e.visargadeva=2307;e.visargagujarati=2691;e.vmonospace=65366;e.voarmenian=1400;e.voicediterationhiragana=12446;e.voicediterationkatakana=12542;e.voicedmarkkana=12443;e.voicedmarkkanahalfwidth=65438;e.vokatakana=12538;e.vparen=9393;e.vtilde=7805;e.vturned=652;e.vuhiragana=12436;e.vukatakana=12532;e.w=119;e.wacute=7811;e.waekorean=12633;e.wahiragana=12431;e.wakatakana=12527;e.wakatakanahalfwidth=65436;e.wakorean=12632;e.wasmallhiragana=12430;e.wasmallkatakana=12526;e.wattosquare=13143;e.wavedash=12316;e.wavyunderscorevertical=65076;e.wawarabic=1608;e.wawfinalarabic=65262;e.wawhamzaabovearabic=1572;e.wawhamzaabovefinalarabic=65158;e.wbsquare=13277;e.wcircle=9446;e.wcircumflex=373;e.wdieresis=7813;e.wdotaccent=7815;e.wdotbelow=7817;e.wehiragana=12433;e.weierstrass=8472;e.wekatakana=12529;e.wekorean=12638;e.weokorean=12637;e.wgrave=7809;e.whitebullet=9702;e.whitecircle=9675;e.whitecircleinverse=9689;e.whitecornerbracketleft=12302;e.whitecornerbracketleftvertical=65091;e.whitecornerbracketright=12303;e.whitecornerbracketrightvertical=65092;e.whitediamond=9671;e.whitediamondcontainingblacksmalldiamond=9672;e.whitedownpointingsmalltriangle=9663;e.whitedownpointingtriangle=9661;e.whiteleftpointingsmalltriangle=9667;e.whiteleftpointingtriangle=9665;e.whitelenticularbracketleft=12310;e.whitelenticularbracketright=12311;e.whiterightpointingsmalltriangle=9657;e.whiterightpointingtriangle=9655;e.whitesmallsquare=9643;e.whitesmilingface=9786;e.whitesquare=9633;e.whitestar=9734;e.whitetelephone=9743;e.whitetortoiseshellbracketleft=12312;e.whitetortoiseshellbracketright=12313;e.whiteuppointingsmalltriangle=9653;e.whiteuppointingtriangle=9651;e.wihiragana=12432;e.wikatakana=12528;e.wikorean=12639;e.wmonospace=65367;e.wohiragana=12434;e.wokatakana=12530;e.wokatakanahalfwidth=65382;e.won=8361;e.wonmonospace=65510;e.wowaenthai=3623;e.wparen=9394;e.wring=7832;e.wsuperior=695;e.wturned=653;e.wynn=447;e.x=120;e.xabovecmb=829;e.xbopomofo=12562;e.xcircle=9447;e.xdieresis=7821;e.xdotaccent=7819;e.xeharmenian=1389;e.xi=958;e.xmonospace=65368;e.xparen=9395;e.xsuperior=739;e.y=121;e.yaadosquare=13134;e.yabengali=2479;e.yacute=253;e.yadeva=2351;e.yaekorean=12626;e.yagujarati=2735;e.yagurmukhi=2607;e.yahiragana=12420;e.yakatakana=12516;e.yakatakanahalfwidth=65428;e.yakorean=12625;e.yamakkanthai=3662;e.yasmallhiragana=12419;e.yasmallkatakana=12515;e.yasmallkatakanahalfwidth=65388;e.yatcyrillic=1123;e.ycircle=9448;e.ycircumflex=375;e.ydieresis=255;e.ydotaccent=7823;e.ydotbelow=7925;e.yeharabic=1610;e.yehbarreearabic=1746;e.yehbarreefinalarabic=64431;e.yehfinalarabic=65266;e.yehhamzaabovearabic=1574;e.yehhamzaabovefinalarabic=65162;e.yehhamzaaboveinitialarabic=65163;e.yehhamzaabovemedialarabic=65164;e.yehinitialarabic=65267;e.yehmedialarabic=65268;e.yehmeeminitialarabic=64733;e.yehmeemisolatedarabic=64600;e.yehnoonfinalarabic=64660;e.yehthreedotsbelowarabic=1745;e.yekorean=12630;e.yen=165;e.yenmonospace=65509;e.yeokorean=12629;e.yeorinhieuhkorean=12678;e.yerahbenyomohebrew=1450;e.yerahbenyomolefthebrew=1450;e.yericyrillic=1099;e.yerudieresiscyrillic=1273;e.yesieungkorean=12673;e.yesieungpansioskorean=12675;e.yesieungsioskorean=12674;e.yetivhebrew=1434;e.ygrave=7923;e.yhook=436;e.yhookabove=7927;e.yiarmenian=1397;e.yicyrillic=1111;e.yikorean=12642;e.yinyang=9775;e.yiwnarmenian=1410;e.ymonospace=65369;e.yod=1497;e.yoddagesh=64313;e.yoddageshhebrew=64313;e.yodhebrew=1497;e.yodyodhebrew=1522;e.yodyodpatahhebrew=64287;e.yohiragana=12424;e.yoikorean=12681;e.yokatakana=12520;e.yokatakanahalfwidth=65430;e.yokorean=12635;e.yosmallhiragana=12423;e.yosmallkatakana=12519;e.yosmallkatakanahalfwidth=65390;e.yotgreek=1011;e.yoyaekorean=12680;e.yoyakorean=12679;e.yoyakthai=3618;e.yoyingthai=3597;e.yparen=9396;e.ypogegrammeni=890;e.ypogegrammenigreekcmb=837;e.yr=422;e.yring=7833;e.ysuperior=696;e.ytilde=7929;e.yturned=654;e.yuhiragana=12422;e.yuikorean=12684;e.yukatakana=12518;e.yukatakanahalfwidth=65429;e.yukorean=12640;e.yusbigcyrillic=1131;e.yusbigiotifiedcyrillic=1133;e.yuslittlecyrillic=1127;e.yuslittleiotifiedcyrillic=1129;e.yusmallhiragana=12421;e.yusmallkatakana=12517;e.yusmallkatakanahalfwidth=65389;e.yuyekorean=12683;e.yuyeokorean=12682;e.yyabengali=2527;e.yyadeva=2399;e.z=122;e.zaarmenian=1382;e.zacute=378;e.zadeva=2395;e.zagurmukhi=2651;e.zaharabic=1592;e.zahfinalarabic=65222;e.zahinitialarabic=65223;e.zahiragana=12374;e.zahmedialarabic=65224;e.zainarabic=1586;e.zainfinalarabic=65200;e.zakatakana=12470;e.zaqefgadolhebrew=1429;e.zaqefqatanhebrew=1428;e.zarqahebrew=1432;e.zayin=1494;e.zayindagesh=64310;e.zayindageshhebrew=64310;e.zayinhebrew=1494;e.zbopomofo=12567;e.zcaron=382;e.zcircle=9449;e.zcircumflex=7825;e.zcurl=657;e.zdot=380;e.zdotaccent=380;e.zdotbelow=7827;e.zecyrillic=1079;e.zedescendercyrillic=1177;e.zedieresiscyrillic=1247;e.zehiragana=12380;e.zekatakana=12476;e.zero=48;e.zeroarabic=1632;e.zerobengali=2534;e.zerodeva=2406;e.zerogujarati=2790;e.zerogurmukhi=2662;e.zerohackarabic=1632 +;e.zeroinferior=8320;e.zeromonospace=65296;e.zerooldstyle=63280;e.zeropersian=1776;e.zerosuperior=8304;e.zerothai=3664;e.zerowidthjoiner=65279;e.zerowidthnonjoiner=8204;e.zerowidthspace=8203;e.zeta=950;e.zhbopomofo=12563;e.zhearmenian=1386;e.zhebrevecyrillic=1218;e.zhecyrillic=1078;e.zhedescendercyrillic=1175;e.zhedieresiscyrillic=1245;e.zihiragana=12376;e.zikatakana=12472;e.zinorhebrew=1454;e.zlinebelow=7829;e.zmonospace=65370;e.zohiragana=12382;e.zokatakana=12478;e.zparen=9397;e.zretroflexhook=656;e.zstroke=438;e.zuhiragana=12378;e.zukatakana=12474;e[".notdef"]=0;e.angbracketleftbig=9001;e.angbracketleftBig=9001;e.angbracketleftbigg=9001;e.angbracketleftBigg=9001;e.angbracketrightBig=9002;e.angbracketrightbig=9002;e.angbracketrightBigg=9002;e.angbracketrightbigg=9002;e.arrowhookleft=8618;e.arrowhookright=8617;e.arrowlefttophalf=8636;e.arrowleftbothalf=8637;e.arrownortheast=8599;e.arrownorthwest=8598;e.arrowrighttophalf=8640;e.arrowrightbothalf=8641;e.arrowsoutheast=8600;e.arrowsouthwest=8601;e.backslashbig=8726;e.backslashBig=8726;e.backslashBigg=8726;e.backslashbigg=8726;e.bardbl=8214;e.bracehtipdownleft=65079;e.bracehtipdownright=65079;e.bracehtipupleft=65080;e.bracehtipupright=65080;e.braceleftBig=123;e.braceleftbig=123;e.braceleftbigg=123;e.braceleftBigg=123;e.bracerightBig=125;e.bracerightbig=125;e.bracerightbigg=125;e.bracerightBigg=125;e.bracketleftbig=91;e.bracketleftBig=91;e.bracketleftbigg=91;e.bracketleftBigg=91;e.bracketrightBig=93;e.bracketrightbig=93;e.bracketrightbigg=93;e.bracketrightBigg=93;e.ceilingleftbig=8968;e.ceilingleftBig=8968;e.ceilingleftBigg=8968;e.ceilingleftbigg=8968;e.ceilingrightbig=8969;e.ceilingrightBig=8969;e.ceilingrightbigg=8969;e.ceilingrightBigg=8969;e.circledotdisplay=8857;e.circledottext=8857;e.circlemultiplydisplay=8855;e.circlemultiplytext=8855;e.circleplusdisplay=8853;e.circleplustext=8853;e.contintegraldisplay=8750;e.contintegraltext=8750;e.coproductdisplay=8720;e.coproducttext=8720;e.floorleftBig=8970;e.floorleftbig=8970;e.floorleftbigg=8970;e.floorleftBigg=8970;e.floorrightbig=8971;e.floorrightBig=8971;e.floorrightBigg=8971;e.floorrightbigg=8971;e.hatwide=770;e.hatwider=770;e.hatwidest=770;e.intercal=7488;e.integraldisplay=8747;e.integraltext=8747;e.intersectiondisplay=8898;e.intersectiontext=8898;e.logicalanddisplay=8743;e.logicalandtext=8743;e.logicalordisplay=8744;e.logicalortext=8744;e.parenleftBig=40;e.parenleftbig=40;e.parenleftBigg=40;e.parenleftbigg=40;e.parenrightBig=41;e.parenrightbig=41;e.parenrightBigg=41;e.parenrightbigg=41;e.prime=8242;e.productdisplay=8719;e.producttext=8719;e.radicalbig=8730;e.radicalBig=8730;e.radicalBigg=8730;e.radicalbigg=8730;e.radicalbt=8730;e.radicaltp=8730;e.radicalvertex=8730;e.slashbig=47;e.slashBig=47;e.slashBigg=47;e.slashbigg=47;e.summationdisplay=8721;e.summationtext=8721;e.tildewide=732;e.tildewider=732;e.tildewidest=732;e.uniondisplay=8899;e.unionmultidisplay=8846;e.unionmultitext=8846;e.unionsqdisplay=8852;e.unionsqtext=8852;e.uniontext=8899;e.vextenddouble=8741;e.vextendsingle=8739}),s=i(function(e){e.space=32;e.a1=9985;e.a2=9986;e.a202=9987;e.a3=9988;e.a4=9742;e.a5=9990;e.a119=9991;e.a118=9992;e.a117=9993;e.a11=9755;e.a12=9758;e.a13=9996;e.a14=9997;e.a15=9998;e.a16=9999;e.a105=1e4;e.a17=10001;e.a18=10002;e.a19=10003;e.a20=10004;e.a21=10005;e.a22=10006;e.a23=10007;e.a24=10008;e.a25=10009;e.a26=10010;e.a27=10011;e.a28=10012;e.a6=10013;e.a7=10014;e.a8=10015;e.a9=10016;e.a10=10017;e.a29=10018;e.a30=10019;e.a31=10020;e.a32=10021;e.a33=10022;e.a34=10023;e.a35=9733;e.a36=10025;e.a37=10026;e.a38=10027;e.a39=10028;e.a40=10029;e.a41=10030;e.a42=10031;e.a43=10032;e.a44=10033;e.a45=10034;e.a46=10035;e.a47=10036;e.a48=10037;e.a49=10038;e.a50=10039;e.a51=10040;e.a52=10041;e.a53=10042;e.a54=10043;e.a55=10044;e.a56=10045;e.a57=10046;e.a58=10047;e.a59=10048;e.a60=10049;e.a61=10050;e.a62=10051;e.a63=10052;e.a64=10053;e.a65=10054;e.a66=10055;e.a67=10056;e.a68=10057;e.a69=10058;e.a70=10059;e.a71=9679;e.a72=10061;e.a73=9632;e.a74=10063;e.a203=10064;e.a75=10065;e.a204=10066;e.a76=9650;e.a77=9660;e.a78=9670;e.a79=10070;e.a81=9687;e.a82=10072;e.a83=10073;e.a84=10074;e.a97=10075;e.a98=10076;e.a99=10077;e.a100=10078;e.a101=10081;e.a102=10082;e.a103=10083;e.a104=10084;e.a106=10085;e.a107=10086;e.a108=10087;e.a112=9827;e.a111=9830;e.a110=9829;e.a109=9824;e.a120=9312;e.a121=9313;e.a122=9314;e.a123=9315;e.a124=9316;e.a125=9317;e.a126=9318;e.a127=9319;e.a128=9320;e.a129=9321;e.a130=10102;e.a131=10103;e.a132=10104;e.a133=10105;e.a134=10106;e.a135=10107;e.a136=10108;e.a137=10109;e.a138=10110;e.a139=10111;e.a140=10112;e.a141=10113;e.a142=10114;e.a143=10115;e.a144=10116;e.a145=10117;e.a146=10118;e.a147=10119;e.a148=10120;e.a149=10121;e.a150=10122;e.a151=10123;e.a152=10124;e.a153=10125;e.a154=10126;e.a155=10127;e.a156=10128;e.a157=10129;e.a158=10130;e.a159=10131;e.a160=10132;e.a161=8594;e.a163=8596;e.a164=8597;e.a196=10136;e.a165=10137;e.a192=10138;e.a166=10139;e.a167=10140;e.a168=10141;e.a169=10142;e.a170=10143;e.a171=10144;e.a172=10145;e.a173=10146;e.a162=10147;e.a174=10148;e.a175=10149;e.a176=10150;e.a177=10151;e.a178=10152;e.a179=10153;e.a193=10154;e.a180=10155;e.a199=10156;e.a181=10157;e.a200=10158;e.a182=10159;e.a201=10161;e.a183=10162;e.a184=10163;e.a197=10164;e.a185=10165;e.a194=10166;e.a198=10167;e.a186=10168;e.a195=10169;e.a187=10170;e.a188=10171;e.a189=10172;e.a190=10173;e.a191=10174;e.a89=10088;e.a90=10089;e.a93=10090;e.a94=10091;e.a91=10092;e.a92=10093;e.a205=10094;e.a85=10095;e.a206=10096;e.a86=10097;e.a87=10098;e.a88=10099;e.a95=10100;e.a96=10101;e[".notdef"]=0});t.getGlyphsUnicode=n;t.getDingbatsGlyphsUnicode=s},function(e,t,a){"use strict";function r(e){i=e}var i,n=a(0),s=a(1),o=a(33),c=n.UNSUPPORTED_FEATURES,l=n.InvalidPDFException,h=n.MessageHandler,u=n.MissingPDFException,f=n.UnexpectedResponseException,d=n.PasswordException,g=n.UnknownErrorException,p=n.XRefParseException,m=n.arrayByteLength,b=n.arraysToBytes,v=n.assert,y=n.createPromiseCapability,k=n.info,w=n.warn,C=n.setVerbosityLevel,x=n.isNodeJS,S=s.Ref,A=o.LocalPdfManager,I=o.NetworkPdfManager,B=function(){function e(e){this.name=e;this.terminated=!1;this._capability=y()}e.prototype={get finished(){return this._capability.promise},finish:function(){this._capability.resolve()},terminate:function(){this.terminated=!0},ensureNotTerminated:function(){if(this.terminated)throw new Error("Worker task was terminated")}};return e}(),R=function(){function e(e,t){this._queuedChunks=[];var a=e.initialData;a&&a.length>0&&this._queuedChunks.push(a);this._msgHandler=t;this._isRangeSupported=!e.disableRange;this._isStreamingSupported=!e.disableStream;this._contentLength=e.length;this._fullRequestReader=null;this._rangeReaders=[];t.on("OnDataRange",this._onReceiveData.bind(this));t.on("OnDataProgress",this._onProgress.bind(this))}function t(e,t){this._stream=e;this._done=!1;this._queuedChunks=t||[];this._requests=[];this._headersReady=Promise.resolve();e._fullRequestReader=this;this.onProgress=null}function a(e,t,a){this._stream=e;this._begin=t;this._end=a;this._queuedChunk=null;this._requests=[];this._done=!1;this.onProgress=null}e.prototype={_onReceiveData:function(e){if(void 0===e.begin)this._fullRequestReader?this._fullRequestReader._enqueue(e.chunk):this._queuedChunks.push(e.chunk);else{var t=this._rangeReaders.some(function(t){if(t._begin!==e.begin)return!1;t._enqueue(e.chunk);return!0});v(t)}},_onProgress:function(e){if(this._rangeReaders.length>0){var t=this._rangeReaders[0];t.onProgress&&t.onProgress({loaded:e.loaded})}},_removeRangeReader:function(e){var t=this._rangeReaders.indexOf(e);t>=0&&this._rangeReaders.splice(t,1)},getFullReader:function(){v(!this._fullRequestReader);var e=this._queuedChunks;this._queuedChunks=null;return new t(this,e)},getRangeReader:function(e,t){var r=new a(this,e,t);this._msgHandler.send("RequestDataRange",{begin:e,end:t});this._rangeReaders.push(r);return r},cancelAllRequests:function(e){this._fullRequestReader&&this._fullRequestReader.cancel(e);this._rangeReaders.slice(0).forEach(function(t){t.cancel(e)})}};t.prototype={_enqueue:function(e){if(!this._done)if(this._requests.length>0){var t=this._requests.shift();t.resolve({value:e,done:!1})}else this._queuedChunks.push(e)},get headersReady(){return this._headersReady},get isRangeSupported(){return this._stream._isRangeSupported},get isStreamingSupported(){return this._stream._isStreamingSupported},get contentLength(){return this._stream._contentLength},read:function(){if(this._queuedChunks.length>0){var e=this._queuedChunks.shift();return Promise.resolve({value:e,done:!1})}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=y();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}};a.prototype={_enqueue:function(e){if(!this._done){if(0===this._requests.length)this._queuedChunk=e;else{this._requests.shift().resolve({value:e,done:!1});this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}this._done=!0;this._stream._removeRangeReader(this)}},get isStreamingSupported(){return!1},read:function(){if(this._queuedChunk)return Promise.resolve({value:this._queuedChunk,done:!1});if(this._done)return Promise.resolve({value:void 0,done:!0});var e=y();this._requests.push(e);return e.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._stream._removeRangeReader(this)}};return e}(),T={setup:function(e,t){var a=!1;e.on("test",function(t){if(!a){a=!0;if(t instanceof Uint8Array){var r=255===t[0];e.postMessageTransfers=r;var i=new XMLHttpRequest,n="response"in i;try{i.responseType}catch(e){n=!1}n?e.send("test",{supportTypedArray:!0,supportTransfers:r}):e.send("test",!1)}else e.send("test","main",!1)}});e.on("configure",function(e){C(e.verbosity)});e.on("GetDocRequest",function(e){return T.createDocumentHandler(e,t)})},createDocumentHandler:function(e,t){function a(){if(T)throw new Error("Worker was terminated")}function r(e){P.push(e)}function n(e){e.finish();var t=P.indexOf(e);P.splice(t,1)}function s(e){var t=y(),a=function(){var e=x.ensureDoc("numPages"),a=x.ensureDoc("fingerprint"),i=x.ensureXRef("encrypt");Promise.all([e,a,i]).then(function(e){var a={numPages:e[0],fingerprint:e[1],encrypted:!!e[2]};t.resolve(a)},r)},r=function(e){t.reject(e)};x.ensureDoc("checkHeader",[]).then(function(){x.ensureDoc("parseStartXRef",[]).then(function(){x.ensureDoc("parse",[e]).then(a,r)},r)},r);return t.promise}function o(e,t){var r,n=y(),s=e.source;if(s.data){try{r=new A(M,s.data,s.password,t,E);n.resolve(r)}catch(e){n.reject(e)}return n.promise}var o;try{if(s.chunkedViewerLoading)o=new R(s,D);else{v(i,"pdfjs/core/network module is not loaded");o=new i(e)}}catch(e){n.reject(e);return n.promise}var c=o.getFullReader();c.headersReady.then(function(){c.isStreamingSupported&&c.isRangeSupported||(c.onProgress=function(e){D.send("DocProgress",{loaded:e.loaded,total:e.total})});if(c.isRangeSupported){var e=s.disableAutoFetch||c.isStreamingSupported;r=new I(M,o,{msgHandler:D,url:s.url,password:s.password,length:c.contentLength,disableAutoFetch:e,rangeChunkSize:s.rangeChunkSize},t,E);n.resolve(r);O=null}}).catch(function(e){n.reject(e);O=null});var l=[],h=0,u=function(){var e=b(l);s.length&&e.length!==s.length&&w("reported HTTP length is different from actual");try{r=new A(M,e,s.password,t,E);n.resolve(r)}catch(e){n.reject(e)}l=[]};new Promise(function(e,t){var i=function(e){try{a();if(e.done){r||u();O=null;return}var n=e.value;h+=m(n);c.isStreamingSupported||D.send("DocProgress",{loaded:h,total:Math.max(h,c.contentLength||0)});r?r.sendProgressiveData(n):l.push(n);c.read().then(i,t)}catch(e){t(e)}};c.read().then(i,t)}).catch(function(e){n.reject(e);O=null});O=function(){o.cancelAllRequests("abort")};return n.promise}function C(e){function t(e){a();D.send("GetDoc",{pdfInfo:e})}function i(e){if(e instanceof d){var t=new B("PasswordException: response "+e.code);r(t);D.sendWithPromise("PasswordRequest",e).then(function(e){n(t);x.updatePassword(e.password);c()}).catch(function(e){n(t);D.send("PasswordException",e)}.bind(null,e))}else e instanceof l?D.send("InvalidPDF",e):e instanceof u?D.send("MissingPDF",e):e instanceof f?D.send("UnexpectedResponse",e):D.send("UnknownError",new g(e.message,e.toString()))}function c(){a();s(!1).then(t,function(e){a();if(e instanceof p){x.requestLoadedStream();x.onLoadedStream().then(function(){a();s(!0).then(t,i)})}else i(e)},i)}a();o(e,{forceDataSchema:e.disableCreateObjectURL,maxImageSize:void 0===e.maxImageSize?-1:e.maxImageSize,disableFontFace:e.disableFontFace,disableNativeImageDecoder:e.disableNativeImageDecoder}).then(function(e){if(T){e.terminate();throw new Error("Worker was terminated")}x=e;D.send("PDFManagerReady",null);x.onLoadedStream().then(function(e){D.send("DataLoaded",{length:e.bytes.byteLength})})}).then(c,i)}var x,T=!1,O=null,P=[],M=e.docId,E=e.docBaseUrl,L=e.docId+"_worker",D=new h(L,M,t);D.postMessageTransfers=e.postMessageTransfers;D.on("GetPage",function(e){return x.getPage(e.pageIndex).then(function(e){var t=x.ensure(e,"rotate"),a=x.ensure(e,"ref"),r=x.ensure(e,"userUnit"),i=x.ensure(e,"view");return Promise.all([t,a,r,i]).then(function(e){return{rotate:e[0],ref:e[1],userUnit:e[2],view:e[3]}})})});D.on("GetPageIndex",function(e){var t=new S(e.ref.num,e.ref.gen);return x.pdfDocument.catalog.getPageIndex(t)});D.on("GetDestinations",function(e){return x.ensureCatalog("destinations")});D.on("GetDestination",function(e){return x.ensureCatalog("getDestination",[e.id])});D.on("GetPageLabels",function(e){return x.ensureCatalog("pageLabels")});D.on("GetAttachments",function(e){return x.ensureCatalog("attachments")});D.on("GetJavaScript",function(e){return x.ensureCatalog("javaScript")});D.on("GetOutline",function(e){return x.ensureCatalog("documentOutline")});D.on("GetMetadata",function(e){return Promise.all([x.ensureDoc("documentInfo"),x.ensureCatalog("metadata")])});D.on("GetData",function(e){x.requestLoadedStream();return x.onLoadedStream().then(function(e){return e.bytes})});D.on("GetStats",function(e){return x.pdfDocument.xref.stats});D.on("GetAnnotations",function(e){return x.getPage(e.pageIndex).then(function(t){return x.ensure(t,"getAnnotationsData",[e.intent])})});D.on("RenderPageRequest",function(e){var t=e.pageIndex;x.getPage(t).then(function(a){var i=new B("RenderPageRequest: page "+t);r(i);var s=t+1,o=Date.now();a.getOperatorList(D,i,e.intent,e.renderInteractiveForms).then(function(e){n(i);k("page="+s+" - getOperatorList: time="+(Date.now()-o)+"ms, len="+e.totalLength)},function(t){n(i);if(!i.terminated){D.send("UnsupportedFeature",{featureId:c.unknown});var a,r="worker.js: while trying to getPage() and getOperatorList()";a="string"==typeof t?{message:t,stack:r}:"object"==typeof t?{message:t.message||t.toString(),stack:t.stack||r}:{message:"Unknown exception type: "+typeof t,stack:r};D.send("PageError",{pageNum:s,error:a,intent:e.intent})}})})},this);D.on("GetTextContent",function(e){var t=e.pageIndex,a=e.normalizeWhitespace,i=e.combineTextItems;return x.getPage(t).then(function(e){var s=new B("GetTextContent: page "+t);r(s);var o=t+1,c=Date.now();return e.extractTextContent(D,s,a,i).then(function(e){n(s);k("text indexing: page="+o+" - time="+(Date.now()-c)+"ms");return e},function(e){n(s);if(!s.terminated)throw e})})});D.on("Cleanup",function(e){return x.cleanup()});D.on("Terminate",function(e){T=!0;if(x){x.terminate();x=null}O&&O();var t=[];P.forEach(function(e){t.push(e.finished);e.terminate()});return Promise.all(t).then(function(){D.destroy();D=null})});D.on("Ready",function(t){C(e);e=null});return L}};"undefined"!=typeof window||x()||function(){var e=new h("worker","main",self);T.setup(e,self);e.send("ready",null)}();t.setPDFNetworkStreamClass=r;t.WorkerTask=B;t.WorkerMessageHandler=T},function(e,t,a){"use strict";var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,a){"use strict";var r=function(){function e(e,t,a){this.data=e;this.bp=t;this.dataEnd=a;this.chigh=e[t];this.clow=0;this.byteIn();this.chigh=this.chigh<<7&65535|this.clow>>9&127;this.clow=this.clow<<7&65535;this.ct-=7;this.a=32768}var t=[{qe:22017,nmps:1,nlps:1,switchFlag:1},{qe:13313,nmps:2,nlps:6,switchFlag:0},{qe:6145,nmps:3,nlps:9,switchFlag:0},{qe:2753,nmps:4,nlps:12,switchFlag:0},{qe:1313,nmps:5,nlps:29,switchFlag:0},{qe:545,nmps:38,nlps:33,switchFlag:0},{qe:22017,nmps:7,nlps:6,switchFlag:1},{qe:21505,nmps:8,nlps:14,switchFlag:0},{qe:18433,nmps:9,nlps:14,switchFlag:0},{qe:14337,nmps:10,nlps:14,switchFlag:0},{qe:12289,nmps:11,nlps:17,switchFlag:0},{qe:9217,nmps:12,nlps:18,switchFlag:0},{qe:7169,nmps:13,nlps:20,switchFlag:0},{qe:5633,nmps:29,nlps:21,switchFlag:0},{qe:22017,nmps:15,nlps:14,switchFlag:1},{qe:21505,nmps:16,nlps:14,switchFlag:0},{qe:20737,nmps:17,nlps:15,switchFlag:0},{qe:18433,nmps:18,nlps:16,switchFlag:0},{qe:14337,nmps:19,nlps:17,switchFlag:0},{qe:13313,nmps:20,nlps:18,switchFlag:0},{qe:12289,nmps:21,nlps:19,switchFlag:0},{qe:10241,nmps:22,nlps:19,switchFlag:0},{qe:9217,nmps:23,nlps:20,switchFlag:0},{qe:8705,nmps:24,nlps:21,switchFlag:0},{qe:7169,nmps:25,nlps:22,switchFlag:0},{qe:6145,nmps:26,nlps:23,switchFlag:0},{qe:5633,nmps:27,nlps:24,switchFlag:0},{qe:5121,nmps:28,nlps:25,switchFlag:0},{qe:4609,nmps:29,nlps:26,switchFlag:0},{qe:4353,nmps:30,nlps:27,switchFlag:0},{qe:2753,nmps:31,nlps:28,switchFlag:0},{qe:2497,nmps:32,nlps:29,switchFlag:0},{qe:2209,nmps:33,nlps:30,switchFlag:0},{qe:1313,nmps:34,nlps:31,switchFlag:0},{qe:1089,nmps:35,nlps:32,switchFlag:0},{qe:673,nmps:36,nlps:33,switchFlag:0},{qe:545,nmps:37,nlps:34,switchFlag:0},{qe:321,nmps:38,nlps:35,switchFlag:0},{qe:273,nmps:39,nlps:36,switchFlag:0},{qe:133,nmps:40,nlps:37,switchFlag:0},{qe:73,nmps:41,nlps:38,switchFlag:0},{qe:37,nmps:42,nlps:39,switchFlag:0},{qe:21,nmps:43,nlps:40,switchFlag:0},{qe:9,nmps:44,nlps:41,switchFlag:0},{qe:5,nmps:45,nlps:42,switchFlag:0},{qe:1,nmps:45,nlps:43,switchFlag:0},{qe:22017,nmps:46,nlps:46,switchFlag:0}];e.prototype={byteIn:function(){var e=this.data,t=this.bp;if(255===e[t]){if(e[t+1]>143){this.clow+=65280;this.ct=8}else{t++;this.clow+=e[t]<<9;this.ct=7;this.bp=t}}else{t++;this.clow+=t<this.dataEnd?e[t]<<8:65280;this.ct=8;this.bp=t}if(this.clow>65535){this.chigh+=this.clow>>16;this.clow&=65535}},readBit:function(e,a){var r,i=e[a]>>1,n=1&e[a],s=t[i],o=s.qe,c=this.a-o;if(this.chigh<o)if(c<o){c=o;r=n;i=s.nmps}else{c=o;r=1^n;1===s.switchFlag&&(n=r);i=s.nlps}else{this.chigh-=o;if(0!=(32768&c)){this.a=c;return n}if(c<o){r=1^n;1===s.switchFlag&&(n=r);i=s.nlps}else{r=n;i=s.nmps}}do{0===this.ct&&this.byteIn();c<<=1;this.chigh=this.chigh<<1&65535|this.clow>>15&1;this.clow=this.clow<<1&65535;this.ct--}while(0==(32768&c));this.a=c;e[a]=i<<1|n;return r}};return e}();t.ArithmeticDecoder=r},function(e,t,a){"use strict";var r=a(0),i=a(22),n=a(4),s=r.error,o=r.info,c=r.bytesToString,l=r.warn,h=r.isArray,u=r.Util,f=r.stringToBytes,d=r.assert,g=i.ISOAdobeCharset,p=i.ExpertCharset,m=i.ExpertSubsetCharset,b=n.StandardEncoding,v=n.ExpertEncoding,y=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"],k=function(){function e(e,t,a){this.bytes=e.getBytes();this.properties=t;this.seacAnalysisEnabled=!!a}var t=[null,{id:"hstem",min:2,stackClearing:!0,stem:!0},null,{id:"vstem",min:2,stackClearing:!0,stem:!0},{id:"vmoveto",min:1,stackClearing:!0},{id:"rlineto",min:2,resetStack:!0},{id:"hlineto",min:1,resetStack:!0},{id:"vlineto",min:1,resetStack:!0},{id:"rrcurveto",min:6,resetStack:!0},null,{id:"callsubr",min:1,undefStack:!0},{id:"return",min:0,undefStack:!0},null,null,{id:"endchar",min:0,stackClearing:!0},null,null,null,{id:"hstemhm",min:2,stackClearing:!0,stem:!0},{id:"hintmask",min:0,stackClearing:!0},{id:"cntrmask",min:0,stackClearing:!0},{id:"rmoveto",min:2,stackClearing:!0},{id:"hmoveto",min:1,stackClearing:!0},{id:"vstemhm",min:2,stackClearing:!0,stem:!0},{id:"rcurveline",min:8,resetStack:!0},{id:"rlinecurve",min:8,resetStack:!0},{id:"vvcurveto",min:4,resetStack:!0},{id:"hhcurveto",min:4,resetStack:!0},null,{id:"callgsubr",min:1,undefStack:!0},{id:"vhcurveto",min:4,resetStack:!0},{id:"hvcurveto",min:4,resetStack:!0}],a=[null,null,null,{id:"and",min:2,stackDelta:-1},{id:"or",min:2,stackDelta:-1},{id:"not",min:1,stackDelta:0},null,null,null,{id:"abs",min:1,stackDelta:0},{id:"add",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]+e[t-1]}},{id:"sub",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]-e[t-1]}},{id:"div",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]/e[t-1]}},null,{id:"neg",min:1,stackDelta:0,stackFn:function(e,t){e[t-1]=-e[t-1]}},{id:"eq",min:2,stackDelta:-1},null,null,{id:"drop",min:1,stackDelta:-1},null,{id:"put",min:2,stackDelta:-2},{id:"get",min:1,stackDelta:0},{id:"ifelse",min:4,stackDelta:-3},{id:"random",min:0,stackDelta:1},{id:"mul",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]*e[t-1]}},null,{id:"sqrt",min:1,stackDelta:0},{id:"dup",min:1,stackDelta:1},{id:"exch",min:2,stackDelta:0},{id:"index",min:2,stackDelta:0},{id:"roll",min:3,stackDelta:-2},null,null,null,{id:"hflex",min:7,resetStack:!0},{id:"flex",min:13,resetStack:!0},{id:"hflex1",min:9,resetStack:!0},{id:"flex1",min:11,resetStack:!0}];e.prototype={parse:function(){var e=this.properties,t=new w;this.cff=t;var a=this.parseHeader(),r=this.parseIndex(a.endPos),i=this.parseIndex(r.endPos),n=this.parseIndex(i.endPos),s=this.parseIndex(n.endPos),o=this.parseDict(i.obj.get(0)),c=this.createDict(I,o,t.strings);t.header=a.obj;t.names=this.parseNameIndex(r.obj);t.strings=this.parseStringIndex(n.obj);t.topDict=c;t.globalSubrIndex=s.obj;this.parsePrivateDict(t.topDict);t.isCIDFont=c.hasName("ROS");var l=c.getByName("CharStrings"),h=this.parseIndex(l).obj,u=c.getByName("FontMatrix");u&&(e.fontMatrix=u);var f=c.getByName("FontBBox");if(f){e.ascent=Math.max(f[3],f[1]);e.descent=Math.min(f[1],f[3]);e.ascentScaled=!0}var d,g;if(t.isCIDFont){for(var p=this.parseIndex(c.getByName("FDArray")).obj,m=0,b=p.count;m<b;++m){var v=p.get(m),y=this.createDict(I,this.parseDict(v),t.strings);this.parsePrivateDict(y);t.fdArray.push(y)}g=null;d=this.parseCharsets(c.getByName("charset"),h.count,t.strings,!0);t.fdSelect=this.parseFDSelect(c.getByName("FDSelect"),h.count)}else{d=this.parseCharsets(c.getByName("charset"),h.count,t.strings,!1);g=this.parseEncoding(c.getByName("Encoding"),e,t.strings,d.charset)}t.charset=d;t.encoding=g;var k=this.parseCharStrings(h,c.privateDict.subrsIndex,s.obj,t.fdSelect,t.fdArray);t.charStrings=k.charStrings;t.seacs=k.seacs;t.widths=k.widths;return t},parseHeader:function(){for(var e=this.bytes,t=e.length,a=0;a<t&&1!==e[a];)++a;if(a>=t)s("Invalid CFF header");else if(0!==a){o("cff data is shifted");e=e.subarray(a);this.bytes=e}var r=e[0],i=e[1],n=e[2],c=e[3];return{obj:new C(r,i,n,c),endPos:n}},parseDict:function(e){function t(){for(var t="",r=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"],i=e.length;a<i;){var n=e[a++],s=n>>4,o=15&n;if(15===s)break;t+=r[s];if(15===o)break;t+=r[o]}return parseFloat(t)}var a=0,r=[],i=[];a=0;for(var n=e.length;a<n;){var s=e[a];if(s<=21){12===s&&(s=s<<8|e[++a]);i.push([s,r]);r=[];++a}else r.push(function(){var r=e[a++];if(30===r)return t();if(28===r){r=e[a++];r=(r<<24|e[a++]<<16)>>16;return r}if(29===r){r=e[a++];r=r<<8|e[a++];r=r<<8|e[a++];r=r<<8|e[a++];return r}if(r>=32&&r<=246)return r-139;if(r>=247&&r<=250)return 256*(r-247)+e[a++]+108;if(r>=251&&r<=254)return-256*(r-251)-e[a++]-108;l('CFFParser_parseDict: "'+r+'" is a reserved command.');return NaN}())}return i},parseIndex:function(e){var t,a,r=new S,i=this.bytes,n=i[e++]<<8|i[e++],s=[],o=e;if(0!==n){var c=i[e++],l=e+(n+1)*c-1;for(t=0,a=n+1;t<a;++t){for(var h=0,u=0;u<c;++u){h<<=8;h+=i[e++]}s.push(l+h)}o=s[n]}for(t=0,a=s.length-1;t<a;++t){var f=s[t],d=s[t+1];r.add(i.subarray(f,d))}return{obj:r,endPos:o}},parseNameIndex:function(e){for(var t=[],a=0,r=e.count;a<r;++a){for(var i=e.get(a),n=Math.min(i.length,127),s=[],o=0;o<n;++o){var l=i[o];0!==o||0!==l?s[o]=l<33||l>126||91===l||93===l||40===l||41===l||123===l||125===l||60===l||62===l||47===l||37===l||35===l?95:l:s[o]=l}t.push(c(s))}return t},parseStringIndex:function(e){for(var t=new x,a=0,r=e.count;a<r;++a){var i=e.get(a);t.add(c(i))}return t},createDict:function(e,t,a){for(var r=new e(a),i=0,n=t.length;i<n;++i){var s=t[i],o=s[0],c=s[1];r.setByKey(o,c)}return r},parseCharString:function(e,r,i,n){if(!r||e.callDepth>10)return!1;for(var s=e.stackSize,o=e.stack,c=r.length,h=0;h<c;){var u=r[h++],f=null;if(12===u){var d=r[h++];if(0===d){r[h-2]=139;r[h-1]=22;s=0}else f=a[d]}else if(28===u){o[s]=(r[h]<<24|r[h+1]<<16)>>16;h+=2;s++}else if(14===u){if(s>=4){s-=4;if(this.seacAnalysisEnabled){e.seac=o.slice(s,s+4);return!1}}f=t[u]}else if(u>=32&&u<=246){o[s]=u-139;s++}else if(u>=247&&u<=254){o[s]=u<251?(u-247<<8)+r[h]+108:-(u-251<<8)-r[h]-108;h++;s++}else if(255===u){o[s]=(r[h]<<24|r[h+1]<<16|r[h+2]<<8|r[h+3])/65536;h+=4;s++}else if(19===u||20===u){e.hints+=s>>1;h+=e.hints+7>>3;s%=2;f=t[u]}else{if(10===u||29===u){var g;g=10===u?i:n;if(!g){f=t[u];l("Missing subrsIndex for "+f.id);return!1}var p=32768;g.count<1240?p=107:g.count<33900&&(p=1131);var m=o[--s]+p;if(m<0||m>=g.count||isNaN(m)){f=t[u];l("Out of bounds subrIndex for "+f.id);return!1}e.stackSize=s;e.callDepth++;var b=this.parseCharString(e,g.get(m),i,n);if(!b)return!1;e.callDepth--;s=e.stackSize;continue}if(11===u){e.stackSize=s;return!0}f=t[u]}if(f){f.stem&&(e.hints+=s>>1);if("min"in f&&!e.undefStack&&s<f.min){l("Not enough parameters for "+f.id+"; actual: "+s+", expected: "+f.min);return!1}if(e.firstStackClearing&&f.stackClearing){e.firstStackClearing=!1;s-=f.min;s>=2&&f.stem?s%=2:s>1&&l("Found too many parameters for stack-clearing command");s>0&&o[s-1]>=0&&(e.width=o[s-1])}if("stackDelta"in f){"stackFn"in f&&f.stackFn(o,s);s+=f.stackDelta}else if(f.stackClearing)s=0;else if(f.resetStack){s=0;e.undefStack=!1}else if(f.undefStack){s=0;e.undefStack=!0;e.firstStackClearing=!1}}}e.stackSize=s;return!0},parseCharStrings:function(e,t,a,r,i){for(var n=[],s=[],o=e.count,c=0;c<o;c++){var h=e.get(c),u={callDepth:0,stackSize:0,stack:[],undefStack:!0,hints:0,firstStackClearing:!0,seac:null,width:null},f=!0,d=null;if(r&&i.length){var g=r.getFDIndex(c);if(-1===g){l("Glyph index is not in fd select.");f=!1}if(g>=i.length){l("Invalid fd index for glyph index.");f=!1}f&&(d=i[g].privateDict.subrsIndex)}else t&&(d=t);f&&(f=this.parseCharString(u,h,d,a));null!==u.width&&(s[c]=u.width);null!==u.seac&&(n[c]=u.seac);f||e.set(c,new Uint8Array([14]))}return{charStrings:e,seacs:n,widths:s}},emptyPrivateDictionary:function(e){var t=this.createDict(B,[],e.strings);e.setByKey(18,[0,0]);e.privateDict=t},parsePrivateDict:function(e){if(e.hasName("Private")){var t=e.getByName("Private");if(h(t)&&2===t.length){var a=t[0],r=t[1];if(0===a||r>=this.bytes.length)this.emptyPrivateDictionary(e);else{var i=r+a,n=this.bytes.subarray(r,i),s=this.parseDict(n),o=this.createDict(B,s,e.strings);e.privateDict=o;if(o.getByName("Subrs")){var c=o.getByName("Subrs"),l=r+c;if(0===c||l>=this.bytes.length)this.emptyPrivateDictionary(e);else{var u=this.parseIndex(l);o.subrsIndex=u.obj}}}}else e.removeByName("Private")}else this.emptyPrivateDictionary(e)},parseCharsets:function(e,t,a,r){if(0===e)return new T(!0,R.ISO_ADOBE,g);if(1===e)return new T(!0,R.EXPERT,p);if(2===e)return new T(!0,R.EXPERT_SUBSET,m);var i,n,o,c=this.bytes,l=e,h=c[e++],u=[".notdef"];t-=1;switch(h){case 0:for(o=0;o<t;o++){i=c[e++]<<8|c[e++];u.push(r?i:a.get(i))}break;case 1:for(;u.length<=t;){i=c[e++]<<8|c[e++];n=c[e++];for(o=0;o<=n;o++)u.push(r?i++:a.get(i++))}break;case 2:for(;u.length<=t;){i=c[e++]<<8|c[e++];n=c[e++]<<8|c[e++];for(o=0;o<=n;o++)u.push(r?i++:a.get(i++))}break;default:s("Unknown charset format")}var f=e,d=c.subarray(l,f) +;return new T(!1,h,u,d)},parseEncoding:function(e,t,a,r){var i,n,o,c=Object.create(null),l=this.bytes,h=!1,u=null;if(0===e||1===e){h=!0;i=e;var f=e?v:b;for(n=0,o=r.length;n<o;n++){var d=f.indexOf(r[n]);-1!==d&&(c[d]=n)}}else{var g=e;i=l[e++];switch(127&i){case 0:var p=l[e++];for(n=1;n<=p;n++)c[l[e++]]=n;break;case 1:var m=l[e++],y=1;for(n=0;n<m;n++)for(var k=l[e++],w=l[e++],C=k;C<=k+w;C++)c[C]=y++;break;default:s("Unknown encoding format: "+i+" in CFF")}var x=e;if(128&i){l[g]&=127;!function(){var t=l[e++];for(n=0;n<t;n++){var i=l[e++],s=(l[e++]<<8)+(255&l[e++]);c[i]=r.indexOf(a.get(s))}}()}u=l.subarray(g,x)}i&=127;return new O(h,i,c,u)},parseFDSelect:function(e,t){var a,r,i=e,n=this.bytes,o=n[e++],c=[],h=!1;switch(o){case 0:for(r=0;r<t;++r){var u=n[e++];c.push(u)}a=n.subarray(i,e);break;case 3:var f=n[e++]<<8|n[e++];for(r=0;r<f;++r){var g=n[e++]<<8|n[e++];if(0===r&&0!==g){l("parseFDSelect: The first range must have a first GID of 0 -- trying to recover.");h=!0;g=0}for(var p=n[e++],m=n[e]<<8|n[e+1],b=g;b<m;++b)c.push(p)}e+=2;a=n.subarray(i,e);h&&(a[3]=a[4]=0);break;default:s('parseFDSelect: Unknown format "'+o+'".')}d(c.length===t,"parseFDSelect: Invalid font data.");return new P(c,a)}};return e}(),w=function(){function e(){this.header=null;this.names=[];this.topDict=null;this.strings=new x;this.globalSubrIndex=null;this.encoding=null;this.charset=null;this.charStrings=null;this.fdArray=[];this.fdSelect=null;this.isCIDFont=!1}return e}(),C=function(){function e(e,t,a,r){this.major=e;this.minor=t;this.hdrSize=a;this.offSize=r}return e}(),x=function(){function e(){this.strings=[]}e.prototype={get:function(e){return e>=0&&e<=390?y[e]:e-391<=this.strings.length?this.strings[e-391]:y[0]},add:function(e){this.strings.push(e)},get count(){return this.strings.length}};return e}(),S=function(){function e(){this.objects=[];this.length=0}e.prototype={add:function(e){this.length+=e.length;this.objects.push(e)},set:function(e,t){this.length+=t.length-this.objects[e].length;this.objects[e]=t},get:function(e){return this.objects[e]},get count(){return this.objects.length}};return e}(),A=function(){function e(e,t){this.keyToNameMap=e.keyToNameMap;this.nameToKeyMap=e.nameToKeyMap;this.defaults=e.defaults;this.types=e.types;this.opcodes=e.opcodes;this.order=e.order;this.strings=t;this.values=Object.create(null)}e.prototype={setByKey:function(e,t){if(!(e in this.keyToNameMap))return!1;var a=t.length;if(0===a)return!0;for(var r=0;r<a;r++)if(isNaN(t[r])){l('Invalid CFFDict value: "'+t+'" for key "'+e+'".');return!0}var i=this.types[e];"num"!==i&&"sid"!==i&&"offset"!==i||(t=t[0]);this.values[e]=t;return!0},setByName:function(e,t){e in this.nameToKeyMap||s('Invalid dictionary name "'+e+'"');this.values[this.nameToKeyMap[e]]=t},hasName:function(e){return this.nameToKeyMap[e]in this.values},getByName:function(e){e in this.nameToKeyMap||s('Invalid dictionary name "'+e+'"');var t=this.nameToKeyMap[e];return t in this.values?this.values[t]:this.defaults[t]},removeByName:function(e){delete this.values[this.nameToKeyMap[e]]}};e.createTables=function(e){for(var t={keyToNameMap:{},nameToKeyMap:{},defaults:{},types:{},opcodes:{},order:[]},a=0,r=e.length;a<r;++a){var i=e[a],n=h(i[0])?(i[0][0]<<8)+i[0][1]:i[0];t.keyToNameMap[n]=i[1];t.nameToKeyMap[i[1]]=n;t.types[n]=i[2];t.defaults[n]=i[3];t.opcodes[n]=h(i[0])?i[0]:[i[0]];t.order.push(n)}return t};return e}(),I=function(){function e(e){null===a&&(a=A.createTables(t));A.call(this,a,e);this.privateDict=null}var t=[[[12,30],"ROS",["sid","sid","num"],null],[[12,20],"SyntheticBase","num",null],[0,"version","sid",null],[1,"Notice","sid",null],[[12,0],"Copyright","sid",null],[2,"FullName","sid",null],[3,"FamilyName","sid",null],[4,"Weight","sid",null],[[12,1],"isFixedPitch","num",0],[[12,2],"ItalicAngle","num",0],[[12,3],"UnderlinePosition","num",-100],[[12,4],"UnderlineThickness","num",50],[[12,5],"PaintType","num",0],[[12,6],"CharstringType","num",2],[[12,7],"FontMatrix",["num","num","num","num","num","num"],[.001,0,0,.001,0,0]],[13,"UniqueID","num",null],[5,"FontBBox",["num","num","num","num"],[0,0,0,0]],[[12,8],"StrokeWidth","num",0],[14,"XUID","array",null],[15,"charset","offset",0],[16,"Encoding","offset",0],[17,"CharStrings","offset",0],[18,"Private",["offset","offset"],null],[[12,21],"PostScript","sid",null],[[12,22],"BaseFontName","sid",null],[[12,23],"BaseFontBlend","delta",null],[[12,31],"CIDFontVersion","num",0],[[12,32],"CIDFontRevision","num",0],[[12,33],"CIDFontType","num",0],[[12,34],"CIDCount","num",8720],[[12,35],"UIDBase","num",null],[[12,37],"FDSelect","offset",null],[[12,36],"FDArray","offset",null],[[12,38],"FontName","sid",null]],a=null;e.prototype=Object.create(A.prototype);return e}(),B=function(){function e(e){null===a&&(a=A.createTables(t));A.call(this,a,e);this.subrsIndex=null}var t=[[6,"BlueValues","delta",null],[7,"OtherBlues","delta",null],[8,"FamilyBlues","delta",null],[9,"FamilyOtherBlues","delta",null],[[12,9],"BlueScale","num",.039625],[[12,10],"BlueShift","num",7],[[12,11],"BlueFuzz","num",1],[10,"StdHW","num",null],[11,"StdVW","num",null],[[12,12],"StemSnapH","delta",null],[[12,13],"StemSnapV","delta",null],[[12,14],"ForceBold","num",0],[[12,17],"LanguageGroup","num",0],[[12,18],"ExpansionFactor","num",.06],[[12,19],"initialRandomSeed","num",0],[20,"defaultWidthX","num",0],[21,"nominalWidthX","num",0],[19,"Subrs","offset",null]],a=null;e.prototype=Object.create(A.prototype);return e}(),R={ISO_ADOBE:0,EXPERT:1,EXPERT_SUBSET:2},T=function(){function e(e,t,a,r){this.predefined=e;this.format=t;this.charset=a;this.raw=r}return e}(),O=function(){function e(e,t,a,r){this.predefined=e;this.format=t;this.encoding=a;this.raw=r}return e}(),P=function(){function e(e,t){this.fdSelect=e;this.raw=t}e.prototype={getFDIndex:function(e){return e<0||e>=this.fdSelect.length?-1:this.fdSelect[e]}};return e}(),M=function(){function e(){this.offsets=Object.create(null)}e.prototype={isTracking:function(e){return e in this.offsets},track:function(e,t){e in this.offsets&&s("Already tracking location of "+e);this.offsets[e]=t},offset:function(e){for(var t in this.offsets)this.offsets[t]+=e},setEntryLocation:function(e,t,a){e in this.offsets||s("Not tracking location of "+e);for(var r=a.data,i=this.offsets[e],n=0,o=t.length;n<o;++n){var c=5*n+i,l=c+1,h=c+2,u=c+3,f=c+4;29===r[c]&&0===r[l]&&0===r[h]&&0===r[u]&&0===r[f]||s("writing to an offset that is not empty");var d=t[n];r[c]=29;r[l]=d>>24&255;r[h]=d>>16&255;r[u]=d>>8&255;r[f]=255&d}}};return e}(),E=function(){function e(e){this.cff=e}e.prototype={compile:function(){var e=this.cff,t={data:[],length:0,add:function(e){this.data=this.data.concat(e);this.length=this.data.length}},a=this.compileHeader(e.header);t.add(a);var r=this.compileNameIndex(e.names);t.add(r);if(e.isCIDFont&&e.topDict.hasName("FontMatrix")){var i=e.topDict.getByName("FontMatrix");e.topDict.removeByName("FontMatrix");for(var n=0,s=e.fdArray.length;n<s;n++){var o=e.fdArray[n],c=i.slice(0);o.hasName("FontMatrix")&&(c=u.transform(c,o.getByName("FontMatrix")));o.setByName("FontMatrix",c)}}var l=this.compileTopDicts([e.topDict],t.length,e.isCIDFont);t.add(l.output);var h=l.trackers[0],f=this.compileStringIndex(e.strings.strings);t.add(f);var d=this.compileIndex(e.globalSubrIndex);t.add(d);if(e.encoding&&e.topDict.hasName("Encoding"))if(e.encoding.predefined)h.setEntryLocation("Encoding",[e.encoding.format],t);else{var g=this.compileEncoding(e.encoding);h.setEntryLocation("Encoding",[t.length],t);t.add(g)}if(e.charset&&e.topDict.hasName("charset"))if(e.charset.predefined)h.setEntryLocation("charset",[e.charset.format],t);else{var p=this.compileCharset(e.charset);h.setEntryLocation("charset",[t.length],t);t.add(p)}var m=this.compileCharStrings(e.charStrings);h.setEntryLocation("CharStrings",[t.length],t);t.add(m);if(e.isCIDFont){h.setEntryLocation("FDSelect",[t.length],t);var b=this.compileFDSelect(e.fdSelect.raw);t.add(b);l=this.compileTopDicts(e.fdArray,t.length,!0);h.setEntryLocation("FDArray",[t.length],t);t.add(l.output);var v=l.trackers;this.compilePrivateDicts(e.fdArray,v,t)}this.compilePrivateDicts([e.topDict],[h],t);t.add([0]);return t.data},encodeNumber:function(e){return parseFloat(e)!==parseInt(e,10)||isNaN(e)?this.encodeFloat(e):this.encodeInteger(e)},encodeFloat:function(e){var t=e.toString(),a=/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(t);if(a){var r=parseFloat("1e"+((a[2]?+a[2]:0)+a[1].length));t=(Math.round(e*r)/r).toString()}var i,n,s="";for(i=0,n=t.length;i<n;++i){var o=t[i];s+="e"===o?"-"===t[++i]?"c":"b":"."===o?"a":"-"===o?"e":o}s+=1&s.length?"f":"ff";var c=[30];for(i=0,n=s.length;i<n;i+=2)c.push(parseInt(s.substr(i,2),16));return c},encodeInteger:function(e){var t;if(e>=-107&&e<=107)t=[e+139];else if(e>=108&&e<=1131){e-=108;t=[247+(e>>8),255&e]}else if(e>=-1131&&e<=-108){e=-e-108;t=[251+(e>>8),255&e]}else t=e>=-32768&&e<=32767?[28,e>>8&255,255&e]:[29,e>>24&255,e>>16&255,e>>8&255,255&e];return t},compileHeader:function(e){return[e.major,e.minor,e.hdrSize,e.offSize]},compileNameIndex:function(e){for(var t=new S,a=0,r=e.length;a<r;++a)t.add(f(e[a]));return this.compileIndex(t)},compileTopDicts:function(e,t,a){for(var r=[],i=new S,n=0,s=e.length;n<s;++n){var o=e[n];if(a){o.removeByName("CIDFontVersion");o.removeByName("CIDFontRevision");o.removeByName("CIDFontType");o.removeByName("CIDCount");o.removeByName("UIDBase")}var c=new M,l=this.compileDict(o,c);r.push(c);i.add(l);c.offset(t)}i=this.compileIndex(i,r);return{trackers:r,output:i}},compilePrivateDicts:function(e,t,a){for(var r=0,i=e.length;r<i;++r){var n=e[r];d(n.privateDict&&n.hasName("Private"),"There must be an private dictionary.");var s=n.privateDict,o=new M,c=this.compileDict(s,o),l=a.length;o.offset(l);c.length||(l=0);t[r].setEntryLocation("Private",[c.length,l],a);a.add(c);if(s.subrsIndex&&s.hasName("Subrs")){var h=this.compileIndex(s.subrsIndex);o.setEntryLocation("Subrs",[c.length],a);a.add(h)}}},compileDict:function(e,t){for(var a=[],r=e.order,i=0;i<r.length;++i){var n=r[i];if(n in e.values){var o=e.values[n],c=e.types[n];h(c)||(c=[c]);h(o)||(o=[o]);if(0!==o.length){for(var l=0,u=c.length;l<u;++l){var f=c[l],d=o[l];switch(f){case"num":case"sid":a=a.concat(this.encodeNumber(d));break;case"offset":var g=e.keyToNameMap[n];t.isTracking(g)||t.track(g,a.length);a=a.concat([29,0,0,0,0]);break;case"array":case"delta":a=a.concat(this.encodeNumber(d));for(var p=1,m=o.length;p<m;++p)a=a.concat(this.encodeNumber(o[p]));break;default:s("Unknown data type of "+f)}}a=a.concat(e.opcodes[n])}}}return a},compileStringIndex:function(e){for(var t=new S,a=0,r=e.length;a<r;++a)t.add(f(e[a]));return this.compileIndex(t)},compileGlobalSubrIndex:function(){var e=this.cff.globalSubrIndex;this.out.writeByteArray(this.compileIndex(e))},compileCharStrings:function(e){return this.compileIndex(e)},compileCharset:function(e){return this.compileTypedArray(e.raw)},compileEncoding:function(e){return this.compileTypedArray(e.raw)},compileFDSelect:function(e){return this.compileTypedArray(e)},compileTypedArray:function(e){for(var t=[],a=0,r=e.length;a<r;++a)t[a]=e[a];return t},compileIndex:function(e,t){t=t||[];var a=e.objects,r=a.length;if(0===r)return[0,0,0];var i,n=[r>>8&255,255&r],s=1;for(i=0;i<r;++i)s+=a[i].length;var o;o=s<256?1:s<65536?2:s<16777216?3:4;n.push(o);var c=1;for(i=0;i<r+1;i++){1===o?n.push(255&c):2===o?n.push(c>>8&255,255&c):3===o?n.push(c>>16&255,c>>8&255,255&c):n.push(c>>>24&255,c>>16&255,c>>8&255,255&c);a[i]&&(c+=a[i].length)}for(i=0;i<r;i++){t[i]&&t[i].offset(n.length);for(var l=0,h=a[i].length;l<h;l++)n.push(a[i][l])}return n}};return e}();t.CFFStandardStrings=y;t.CFFParser=k;t.CFF=w;t.CFFHeader=C;t.CFFStrings=x;t.CFFIndex=S;t.CFFCharset=T;t.CFFTopDict=I;t.CFFPrivateDict=B;t.CFFCompiler=E},function(e,t,a){"use strict";var r=a(0),i=r.MissingDataException,n=r.arrayByteLength,s=r.arraysToBytes,o=r.assert,c=r.createPromiseCapability,l=r.isInt,h=r.isEmptyObj,u=function(){function e(e,t,a){this.bytes=new Uint8Array(e);this.start=0;this.pos=0;this.end=e;this.chunkSize=t;this.loadedChunks=[];this.numChunksLoaded=0;this.numChunks=Math.ceil(e/t);this.manager=a;this.progressiveDataLength=0;this.lastSuccessfulEnsureByteChunk=-1}e.prototype={getMissingChunks:function(){for(var e=[],t=0,a=this.numChunks;t<a;++t)this.loadedChunks[t]||e.push(t);return e},getBaseStreams:function(){return[this]},allChunksLoaded:function(){return this.numChunksLoaded===this.numChunks},onReceiveData:function(e,t){var a=e+t.byteLength;o(e%this.chunkSize==0,"Bad begin offset: "+e);var r=this.bytes.length;o(a%this.chunkSize==0||a===r,"Bad end offset: "+a);this.bytes.set(new Uint8Array(t),e);var i,n=this.chunkSize,s=Math.floor(e/n),c=Math.floor((a-1)/n)+1;for(i=s;i<c;++i)if(!this.loadedChunks[i]){this.loadedChunks[i]=!0;++this.numChunksLoaded}},onReceiveProgressiveData:function(e){var t=this.progressiveDataLength,a=Math.floor(t/this.chunkSize);this.bytes.set(new Uint8Array(e),t);t+=e.byteLength;this.progressiveDataLength=t;var r,i=t>=this.end?this.numChunks:Math.floor(t/this.chunkSize);for(r=a;r<i;++r)if(!this.loadedChunks[r]){this.loadedChunks[r]=!0;++this.numChunksLoaded}},ensureByte:function(e){var t=Math.floor(e/this.chunkSize);if(t!==this.lastSuccessfulEnsureByteChunk){if(!this.loadedChunks[t])throw new i(e,e+1);this.lastSuccessfulEnsureByteChunk=t}},ensureRange:function(e,t){if(!(e>=t||t<=this.progressiveDataLength))for(var a=this.chunkSize,r=Math.floor(e/a),n=Math.floor((t-1)/a)+1,s=r;s<n;++s)if(!this.loadedChunks[s])throw new i(e,t)},nextEmptyChunk:function(e){for(var t,a=this.numChunks,r=0;r<a;++r){t=(e+r)%a;if(!this.loadedChunks[t])return t}return null},hasChunk:function(e){return!!this.loadedChunks[e]},get length(){return this.end-this.start},get isEmpty(){return 0===this.length},getByte:function(){var e=this.pos;if(e>=this.end)return-1;this.ensureByte(e);return this.bytes[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t=this.bytes,a=this.pos,r=this.end;if(!e){this.ensureRange(a,r);return t.subarray(a,r)}var i=a+e;i>r&&(i=r);this.ensureRange(a,i);this.pos=i;return t.subarray(a,i)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},getByteRange:function(e,t){this.ensureRange(e,t);return this.bytes.subarray(e,t)},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=this.start},moveStart:function(){this.start=this.pos},makeSubStream:function(e,t,a){function r(){}this.ensureRange(e,e+t);r.prototype=Object.create(this);r.prototype.getMissingChunks=function(){for(var e=this.chunkSize,t=Math.floor(this.start/e),a=Math.floor((this.end-1)/e)+1,r=[],i=t;i<a;++i)this.loadedChunks[i]||r.push(i);return r};var i=new r;i.pos=i.start=e;i.end=e+t||this.end;i.dict=a;return i}};return e}(),f=function(){function e(e,t){var a=t.rangeChunkSize,r=t.length;this.stream=new u(r,a,this);this.length=r;this.chunkSize=a;this.pdfNetworkStream=e;this.url=t.url;this.disableAutoFetch=t.disableAutoFetch;this.msgHandler=t.msgHandler;this.currRequestId=0;this.chunksNeededByRequest=Object.create(null);this.requestsByChunk=Object.create(null);this.promisesByRequest=Object.create(null);this.progressiveDataLength=0;this.aborted=!1;this._loadedStreamCapability=c()}e.prototype={onLoadedStream:function(){return this._loadedStreamCapability.promise},sendRequest:function(e,t){var a=this.pdfNetworkStream.getRangeReader(e,t);a.isStreamingSupported||(a.onProgress=this.onProgress.bind(this));var r=[],i=0,o=this;new Promise(function(e,t){var c=function(l){try{if(!l.done){var h=l.value;r.push(h);i+=n(h);a.isStreamingSupported&&o.onProgress({loaded:i});a.read().then(c,t);return}var u=s(r);r=null;e(u)}catch(e){t(e)}};a.read().then(c,t)}).then(function(t){this.aborted||this.onReceiveData({chunk:t,begin:e})}.bind(this))},requestAllChunks:function(){var e=this.stream.getMissingChunks();this._requestChunks(e);return this._loadedStreamCapability.promise},_requestChunks:function(e){var t,a,r=this.currRequestId++,i=Object.create(null);this.chunksNeededByRequest[r]=i;for(t=0,a=e.length;t<a;t++)this.stream.hasChunk(e[t])||(i[e[t]]=!0);if(h(i))return Promise.resolve();var n=c();this.promisesByRequest[r]=n;var s=[];for(var o in i){o|=0;if(!(o in this.requestsByChunk)){this.requestsByChunk[o]=[];s.push(o)}this.requestsByChunk[o].push(r)}if(!s.length)return n.promise;var l=this.groupChunks(s);for(t=0;t<l.length;++t){var u=l[t],f=u.beginChunk*this.chunkSize,d=Math.min(u.endChunk*this.chunkSize,this.length);this.sendRequest(f,d)}return n.promise},getStream:function(){return this.stream},requestRange:function(e,t){t=Math.min(t,this.length);for(var a=this.getBeginChunk(e),r=this.getEndChunk(t),i=[],n=a;n<r;++n)i.push(n);return this._requestChunks(i)},requestRanges:function(e){e=e||[];for(var t=[],a=0;a<e.length;a++)for(var r=this.getBeginChunk(e[a].begin),i=this.getEndChunk(e[a].end),n=r;n<i;++n)t.indexOf(n)<0&&t.push(n);t.sort(function(e,t){return e-t});return this._requestChunks(t)},groupChunks:function(e){for(var t=[],a=-1,r=-1,i=0;i<e.length;++i){var n=e[i];a<0&&(a=n);if(r>=0&&r+1!==n){t.push({beginChunk:a,endChunk:r+1});a=n}i+1===e.length&&t.push({beginChunk:a,endChunk:n+1});r=n}return t},onProgress:function(e){var t=this.stream.numChunksLoaded*this.chunkSize+e.loaded;this.msgHandler.send("DocProgress",{loaded:t,total:this.length})},onReceiveData:function(e){var t=e.chunk,a=void 0===e.begin,r=a?this.progressiveDataLength:e.begin,i=r+t.byteLength,n=Math.floor(r/this.chunkSize),s=i<this.length?Math.floor(i/this.chunkSize):Math.ceil(i/this.chunkSize);if(a){this.stream.onReceiveProgressiveData(t);this.progressiveDataLength=i}else this.stream.onReceiveData(r,t);this.stream.allChunksLoaded()&&this._loadedStreamCapability.resolve(this.stream);var o,c,u=[];for(t=n;t<s;++t){var f=this.requestsByChunk[t]||[];delete this.requestsByChunk[t];for(o=0;o<f.length;++o){c=f[o];var d=this.chunksNeededByRequest[c];t in d&&delete d[t];h(d)&&u.push(c)}}if(!this.disableAutoFetch&&h(this.requestsByChunk)){var g;if(1===this.stream.numChunksLoaded){var p=this.stream.numChunks-1;this.stream.hasChunk(p)||(g=p)}else g=this.stream.nextEmptyChunk(s);l(g)&&this._requestChunks([g])}for(o=0;o<u.length;++o){c=u[o];var m=this.promisesByRequest[c];delete this.promisesByRequest[c];m.resolve()}this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize,total:this.length})},onError:function(e){this._loadedStreamCapability.reject(e)},getBeginChunk:function(e){return Math.floor(e/this.chunkSize)},getEndChunk:function(e){return Math.floor((e-1)/this.chunkSize)+1},abort:function(){this.aborted=!0;this.pdfNetworkStream&&this.pdfNetworkStream.cancelAllRequests("abort");for(var e in this.promisesByRequest){this.promisesByRequest[e].reject(new Error("Request was aborted"))}}};return e}();t.ChunkedStream=u;t.ChunkedStreamManager=f},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=r.PasswordException,o=r.PasswordResponses,c=r.bytesToString,l=r.warn,h=r.error,u=r.assert,f=r.isInt,d=r.stringToBytes,g=r.utf8StringToString,p=i.Name,m=i.isName,b=i.isDict,v=n.DecryptStream,y=function(){function e(e){this.a=0;this.b=0;var t,a,r=new Uint8Array(256),i=0,n=e.length;for(t=0;t<256;++t)r[t]=t;for(t=0;t<256;++t){a=r[t];i=i+a+e[t%n]&255;r[t]=r[i];r[i]=a}this.s=r}e.prototype={encryptBlock:function(e){var t,a,r,i=e.length,n=this.a,s=this.b,o=this.s,c=new Uint8Array(i);for(t=0;t<i;++t){n=n+1&255;a=o[n];s=s+a&255;r=o[s];o[n]=r;o[s]=a;c[t]=e[t]^o[a+r&255]}this.a=n;this.b=s;return c}};e.prototype.decryptBlock=e.prototype.encryptBlock;return e}(),k=function(){function e(e,r,i){var n,s,o,c=1732584193,l=-271733879,h=-1732584194,u=271733878,f=i+72&-64,d=new Uint8Array(f);for(n=0;n<i;++n)d[n]=e[r++];d[n++]=128;o=f-8;for(;n<o;)d[n++]=0;d[n++]=i<<3&255;d[n++]=i>>5&255;d[n++]=i>>13&255;d[n++]=i>>21&255;d[n++]=i>>>29&255;d[n++]=0;d[n++]=0;d[n++]=0;var g=new Int32Array(16);for(n=0;n<f;){for(s=0;s<16;++s,n+=4)g[s]=d[n]|d[n+1]<<8|d[n+2]<<16|d[n+3]<<24;var p,m,b=c,v=l,y=h,k=u;for(s=0;s<64;++s){if(s<16){p=v&y|~v&k;m=s}else if(s<32){p=k&v|~k&y;m=5*s+1&15}else if(s<48){p=v^y^k;m=3*s+5&15}else{p=y^(v|~k);m=7*s&15}var w=k,C=b+p+a[s]+g[m]|0,x=t[s];k=y;y=v;v=v+(C<<x|C>>>32-x)|0;b=w}c=c+b|0;l=l+v|0;h=h+y|0;u=u+k|0}return new Uint8Array([255&c,c>>8&255,c>>16&255,c>>>24&255,255&l,l>>8&255,l>>16&255,l>>>24&255,255&h,h>>8&255,h>>16&255,h>>>24&255,255&u,u>>8&255,u>>16&255,u>>>24&255])}var t=new Uint8Array([7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]),a=new Int32Array([-680876936,-389564586,606105819,-1044525330,-176418897,1200080426,-1473231341,-45705983,1770035416,-1958414417,-42063,-1990404162,1804603682,-40341101,-1502002290,1236535329,-165796510,-1069501632,643717713,-373897302,-701558691,38016083,-660478335,-405537848,568446438,-1019803690,-187363961,1163531501,-1444681467,-51403784,1735328473,-1926607734,-378558,-2022574463,1839030562,-35309556,-1530992060,1272893353,-155497632,-1094730640,681279174,-358537222,-722521979,76029189,-640364487,-421815835,530742520,-995338651,-198630844,1126891415,-1416354905,-57434055,1700485571,-1894986606,-1051523,-2054922799,1873313359,-30611744,-1560198380,1309151649,-145523070,-1120210379,718787259,-343485551]);return e}(),w=function(){function e(e,t){this.high=0|e;this.low=0|t}e.prototype={and:function(e){this.high&=e.high;this.low&=e.low},xor:function(e){this.high^=e.high;this.low^=e.low},or:function(e){this.high|=e.high;this.low|=e.low},shiftRight:function(e){if(e>=32){this.low=this.high>>>e-32|0;this.high=0}else{this.low=this.low>>>e|this.high<<32-e;this.high=this.high>>>e|0}},shiftLeft:function(e){if(e>=32){this.high=this.low<<e-32;this.low=0}else{this.high=this.high<<e|this.low>>>32-e;this.low=this.low<<e}},rotateRight:function(e){var t,a;if(32&e){a=this.low;t=this.high}else{t=this.low;a=this.high}e&=31;this.low=t>>>e|a<<32-e;this.high=a>>>e|t<<32-e},not:function(){this.high=~this.high;this.low=~this.low},add:function(e){var t=(this.low>>>0)+(e.low>>>0),a=(this.high>>>0)+(e.high>>>0);t>4294967295&&(a+=1);this.low=0|t;this.high=0|a},copyTo:function(e,t){e[t]=this.high>>>24&255;e[t+1]=this.high>>16&255;e[t+2]=this.high>>8&255;e[t+3]=255&this.high;e[t+4]=this.low>>>24&255;e[t+5]=this.low>>16&255;e[t+6]=this.low>>8&255;e[t+7]=255&this.low},assign:function(e){this.high=e.high;this.low=e.low}};return e}(),C=function(){function e(e,t){return e>>>t|e<<32-t}function t(e,t,a){return e&t^~e&a}function a(e,t,a){return e&t^e&a^t&a}function r(t){return e(t,2)^e(t,13)^e(t,22)}function i(t){return e(t,6)^e(t,11)^e(t,25)}function n(t){return e(t,7)^e(t,18)^t>>>3}function s(t){return e(t,17)^e(t,19)^t>>>10}function o(e,o,l){var h,u,f,d=1779033703,g=3144134277,p=1013904242,m=2773480762,b=1359893119,v=2600822924,y=528734635,k=1541459225,w=64*Math.ceil((l+9)/64),C=new Uint8Array(w);for(h=0;h<l;++h)C[h]=e[o++];C[h++]=128;f=w-8;for(;h<f;)C[h++]=0;C[h++]=0;C[h++]=0;C[h++]=0;C[h++]=l>>>29&255;C[h++]=l>>21&255;C[h++]=l>>13&255;C[h++]=l>>5&255;C[h++]=l<<3&255;var x=new Uint32Array(64);for(h=0;h<w;){for(u=0;u<16;++u){x[u]=C[h]<<24|C[h+1]<<16|C[h+2]<<8|C[h+3];h+=4}for(u=16;u<64;++u)x[u]=s(x[u-2])+x[u-7]+n(x[u-15])+x[u-16]|0;var S,A,I=d,B=g,R=p,T=m,O=b,P=v,M=y,E=k;for(u=0;u<64;++u){S=E+i(O)+t(O,P,M)+c[u]+x[u];A=r(I)+a(I,B,R);E=M;M=P;P=O;O=T+S|0;T=R;R=B;B=I;I=S+A|0}d=d+I|0;g=g+B|0;p=p+R|0;m=m+T|0;b=b+O|0;v=v+P|0;y=y+M|0;k=k+E|0}return new Uint8Array([d>>24&255,d>>16&255,d>>8&255,255&d,g>>24&255,g>>16&255,g>>8&255,255&g,p>>24&255,p>>16&255,p>>8&255,255&p,m>>24&255,m>>16&255,m>>8&255,255&m,b>>24&255,b>>16&255,b>>8&255,255&b,v>>24&255,v>>16&255,v>>8&255,255&v,y>>24&255,y>>16&255,y>>8&255,255&y,k>>24&255,k>>16&255,k>>8&255,255&k])}var c=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];return o}(),x=function(){function e(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.not();i.and(r);e.xor(i)}function t(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.and(r);e.xor(i);i.assign(a);i.and(r);e.xor(i)}function a(e,t,a){e.assign(t);e.rotateRight(28);a.assign(t);a.rotateRight(34);e.xor(a);a.assign(t);a.rotateRight(39);e.xor(a)}function r(e,t,a){e.assign(t);e.rotateRight(14);a.assign(t);a.rotateRight(18);e.xor(a);a.assign(t);a.rotateRight(41);e.xor(a)}function i(e,t,a){e.assign(t);e.rotateRight(1);a.assign(t);a.rotateRight(8);e.xor(a);a.assign(t);a.shiftRight(7);e.xor(a)}function n(e,t,a){e.assign(t);e.rotateRight(19);a.assign(t);a.rotateRight(61);e.xor(a);a.assign(t);a.shiftRight(6);e.xor(a)}function s(s,c,l,h){h=!!h;var u,f,d,g,p,m,b,v;if(h){u=new w(3418070365,3238371032);f=new w(1654270250,914150663);d=new w(2438529370,812702999);g=new w(355462360,4144912697);p=new w(1731405415,4290775857);m=new w(2394180231,1750603025);b=new w(3675008525,1694076839);v=new w(1203062813,3204075428)}else{u=new w(1779033703,4089235720);f=new w(3144134277,2227873595);d=new w(1013904242,4271175723);g=new w(2773480762,1595750129);p=new w(1359893119,2917565137);m=new w(2600822924,725511199);b=new w(528734635,4215389547);v=new w(1541459225,327033209)}var y,k,C,x=128*Math.ceil((l+17)/128),S=new Uint8Array(x);for(y=0;y<l;++y)S[y]=s[c++];S[y++]=128;C=x-16;for(;y<C;)S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=l>>>29&255;S[y++]=l>>21&255;S[y++]=l>>13&255;S[y++]=l>>5&255;S[y++]=l<<3&255;var A=new Array(80);for(y=0;y<80;y++)A[y]=new w(0,0);var I,B=new w(0,0),R=new w(0,0),T=new w(0,0),O=new w(0,0),P=new w(0,0),M=new w(0,0),E=new w(0,0),L=new w(0,0),D=new w(0,0),F=new w(0,0),q=new w(0,0),U=new w(0,0);for(y=0;y<x;){for(k=0;k<16;++k){A[k].high=S[y]<<24|S[y+1]<<16|S[y+2]<<8|S[y+3];A[k].low=S[y+4]<<24|S[y+5]<<16|S[y+6]<<8|S[y+7];y+=8}for(k=16;k<80;++k){I=A[k];n(I,A[k-2],U);I.add(A[k-7]);i(q,A[k-15],U);I.add(q);I.add(A[k-16])}B.assign(u);R.assign(f);T.assign(d);O.assign(g);P.assign(p);M.assign(m);E.assign(b);L.assign(v);for(k=0;k<80;++k){D.assign(L);r(q,P,U);D.add(q);e(q,P,M,E,U);D.add(q);D.add(o[k]);D.add(A[k]);a(F,B,U);t(q,B,R,T,U);F.add(q);I=L;L=E;E=M;M=P;O.add(D);P=O;O=T;T=R;R=B;I.assign(D);I.add(F);B=I}u.add(B);f.add(R);d.add(T);g.add(O);p.add(P);m.add(M);b.add(E);v.add(L)}var N;if(h){N=new Uint8Array(48);u.copyTo(N,0);f.copyTo(N,8);d.copyTo(N,16);g.copyTo(N,24);p.copyTo(N,32);m.copyTo(N,40)}else{N=new Uint8Array(64);u.copyTo(N,0);f.copyTo(N,8);d.copyTo(N,16);g.copyTo(N,24);p.copyTo(N,32);m.copyTo(N,40);b.copyTo(N,48);v.copyTo(N,56)}return N}var o=[new w(1116352408,3609767458),new w(1899447441,602891725),new w(3049323471,3964484399),new w(3921009573,2173295548),new w(961987163,4081628472),new w(1508970993,3053834265),new w(2453635748,2937671579),new w(2870763221,3664609560),new w(3624381080,2734883394),new w(310598401,1164996542),new w(607225278,1323610764),new w(1426881987,3590304994),new w(1925078388,4068182383),new w(2162078206,991336113),new w(2614888103,633803317),new w(3248222580,3479774868),new w(3835390401,2666613458),new w(4022224774,944711139),new w(264347078,2341262773),new w(604807628,2007800933),new w(770255983,1495990901),new w(1249150122,1856431235),new w(1555081692,3175218132),new w(1996064986,2198950837),new w(2554220882,3999719339),new w(2821834349,766784016),new w(2952996808,2566594879),new w(3210313671,3203337956),new w(3336571891,1034457026),new w(3584528711,2466948901),new w(113926993,3758326383),new w(338241895,168717936),new w(666307205,1188179964),new w(773529912,1546045734),new w(1294757372,1522805485),new w(1396182291,2643833823),new w(1695183700,2343527390),new w(1986661051,1014477480),new w(2177026350,1206759142),new w(2456956037,344077627),new w(2730485921,1290863460),new w(2820302411,3158454273),new w(3259730800,3505952657),new w(3345764771,106217008),new w(3516065817,3606008344),new w(3600352804,1432725776),new w(4094571909,1467031594),new w(275423344,851169720),new w(430227734,3100823752),new w(506948616,1363258195),new w(659060556,3750685593),new w(883997877,3785050280),new w(958139571,3318307427),new w(1322822218,3812723403),new w(1537002063,2003034995),new w(1747873779,3602036899),new w(1955562222,1575990012),new w(2024104815,1125592928),new w(2227730452,2716904306),new w(2361852424,442776044),new w(2428436474,593698344),new w(2756734187,3733110249),new w(3204031479,2999351573),new w(3329325298,3815920427),new w(3391569614,3928383900),new w(3515267271,566280711),new w(3940187606,3454069534),new w(4118630271,4000239992),new w(116418474,1914138554),new w(174292421,2731055270),new w(289380356,3203993006),new w(460393269,320620315),new w(685471733,587496836),new w(852142971,1086792851),new w(1017036298,365543100),new w(1126000580,2618297676),new w(1288033470,3409855158),new w(1501505948,4234509866),new w(1607167915,987167468),new w(1816402316,1246189591)];return s}(),S=function(){function e(e,t,a){return x(e,t,a,!0)}return e}(),A=function(){function e(){}e.prototype={decryptBlock:function(e){return e}};return e}(),I=function(){function e(e){var t=new Uint8Array(176);t.set(e);for(var a=16,r=1;a<176;++r){var i=t[a-3],o=t[a-2],c=t[a-1],l=t[a-4];i=s[i];o=s[o];c=s[c];l=s[l];i^=n[r];for(var h=0;h<4;++h){t[a]=i^=t[a-16];a++;t[a]=o^=t[a-16];a++;t[a]=c^=t[a-16];a++;t[a]=l^=t[a-16];a++}}return t}function t(e,t){var a=new Uint8Array(16);a.set(e);var r,i,n,s,c,l;for(i=0,n=160;i<16;++i,++n)a[i]^=t[n];for(r=9;r>=1;--r){s=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=s;s=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=s;a[2]=c;s=a[15];c=a[11];l=a[7];a[15]=a[3];a[11]=s;a[7]=c;a[3]=l;for(i=0;i<16;++i)a[i]=o[a[i]];for(i=0,n=16*r;i<16;++i,++n)a[i]^=t[n];for(i=0;i<16;i+=4){var u=h[a[i]],f=h[a[i+1]],d=h[a[i+2]],g=h[a[i+3]];s=u^f>>>8^f<<24^d>>>16^d<<16^g>>>24^g<<8;a[i]=s>>>24&255;a[i+1]=s>>16&255;a[i+2]=s>>8&255;a[i+3]=255&s}}s=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=s;s=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=s;a[2]=c;s=a[15];c=a[11];l=a[7];a[15]=a[3];a[11]=s;a[7]=c;a[3]=l;for(i=0;i<16;++i){a[i]=o[a[i]];a[i]^=t[i]}return a}function a(e,t){var a,r,i,n,o=new Uint8Array(16);o.set(e);for(h=0;h<16;++h)o[h]^=t[h];for(l=1;l<10;l++){for(h=0;h<16;++h)o[h]=s[o[h]];i=o[1];o[1]=o[5];o[5]=o[9];o[9]=o[13];o[13]=i;i=o[2];r=o[6];o[2]=o[10];o[6]=o[14];o[10]=i;o[14]=r;i=o[3];r=o[7];a=o[11];o[3]=o[15];o[7]=i;o[11]=r;o[15]=a;for(var h=0;h<16;h+=4){var u=o[h+0],f=o[h+1],d=o[h+2],g=o[h+3];a=u^f^d^g;o[h+0]^=a^c[u^f];o[h+1]^=a^c[f^d];o[h+2]^=a^c[d^g];o[h+3]^=a^c[g^u]}for(h=0,n=16*l;h<16;++h,++n)o[h]^=t[n]}for(h=0;h<16;++h)o[h]=s[o[h]];i=o[1];o[1]=o[5];o[5]=o[9];o[9]=o[13];o[13]=i;i=o[2];r=o[6];o[2]=o[10];o[6]=o[14];o[10]=i;o[14]=r;i=o[3];r=o[7];a=o[11];o[3]=o[15];o[7]=i;o[11]=r;o[15]=a;for(h=0,n=160;h<16;++h,++n)o[h]^=t[n];return o}function r(t){this.key=e(t);this.buffer=new Uint8Array(16);this.bufferPosition=0}function i(e,a){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[],h=this.iv;for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){var u=t(o,this.key);for(i=0;i<16;++i)u[i]^=h[i];h=o;l.push(u);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=h;if(0===l.length)return new Uint8Array([]);var f=16*l.length;if(a){var d=l[l.length-1],g=d[15];if(g<=16){for(r=15,n=16-g;r>=n;--r)if(d[r]!==g){g=0;break}f-=g;l[l.length-1]=d.subarray(0,16-g)}}var p=new Uint8Array(f);for(r=0,i=0,n=l.length;r<n;++r,i+=16)p.set(l[r],i);return p} +for(var n=new Uint8Array([141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141]),s=new Uint8Array([99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22]),o=new Uint8Array([82,9,106,213,48,54,165,56,191,64,163,158,129,243,215,251,124,227,57,130,155,47,255,135,52,142,67,68,196,222,233,203,84,123,148,50,166,194,35,61,238,76,149,11,66,250,195,78,8,46,161,102,40,217,36,178,118,91,162,73,109,139,209,37,114,248,246,100,134,104,152,22,212,164,92,204,93,101,182,146,108,112,72,80,253,237,185,218,94,21,70,87,167,141,157,132,144,216,171,0,140,188,211,10,247,228,88,5,184,179,69,6,208,44,30,143,202,63,15,2,193,175,189,3,1,19,138,107,58,145,17,65,79,103,220,234,151,242,207,206,240,180,230,115,150,172,116,34,231,173,53,133,226,249,55,232,28,117,223,110,71,241,26,113,29,41,197,137,111,183,98,14,170,24,190,27,252,86,62,75,198,210,121,32,154,219,192,254,120,205,90,244,31,221,168,51,136,7,199,49,177,18,16,89,39,128,236,95,96,81,127,169,25,181,74,13,45,229,122,159,147,201,156,239,160,224,59,77,174,42,245,176,200,235,187,60,131,83,153,97,23,43,4,126,186,119,214,38,225,105,20,99,85,33,12,125]),c=new Uint8Array(256),l=0;l<256;l++)c[l]=l<128?l<<1:l<<1^27;var h=new Uint32Array([0,235474187,470948374,303765277,941896748,908933415,607530554,708780849,1883793496,2118214995,1817866830,1649639237,1215061108,1181045119,1417561698,1517767529,3767586992,4003061179,4236429990,4069246893,3635733660,3602770327,3299278474,3400528769,2430122216,2664543715,2362090238,2193862645,2835123396,2801107407,3035535058,3135740889,3678124923,3576870512,3341394285,3374361702,3810496343,3977675356,4279080257,4043610186,2876494627,2776292904,3076639029,3110650942,2472011535,2640243204,2403728665,2169303058,1001089995,899835584,666464733,699432150,59727847,226906860,530400753,294930682,1273168787,1172967064,1475418501,1509430414,1942435775,2110667444,1876241833,1641816226,2910219766,2743034109,2976151520,3211623147,2505202138,2606453969,2302690252,2269728455,3711829422,3543599269,3240894392,3475313331,3843699074,3943906441,4178062228,4144047775,1306967366,1139781709,1374988112,1610459739,1975683434,2076935265,1775276924,1742315127,1034867998,866637845,566021896,800440835,92987698,193195065,429456164,395441711,1984812685,2017778566,1784663195,1683407248,1315562145,1080094634,1383856311,1551037884,101039829,135050206,437757123,337553864,1042385657,807962610,573804783,742039012,2531067453,2564033334,2328828971,2227573024,2935566865,2700099354,3001755655,3168937228,3868552805,3902563182,4203181171,4102977912,3736164937,3501741890,3265478751,3433712980,1106041591,1340463100,1576976609,1408749034,2043211483,2009195472,1708848333,1809054150,832877231,1068351396,766945465,599762354,159417987,126454664,361929877,463180190,2709260871,2943682380,3178106961,3009879386,2572697195,2538681184,2236228733,2336434550,3509871135,3745345300,3441850377,3274667266,3910161971,3877198648,4110568485,4211818798,2597806476,2497604743,2261089178,2295101073,2733856160,2902087851,3202437046,2968011453,3936291284,3835036895,4136440770,4169408201,3535486456,3702665459,3467192302,3231722213,2051518780,1951317047,1716890410,1750902305,1113818384,1282050075,1584504582,1350078989,168810852,67556463,371049330,404016761,841739592,1008918595,775550814,540080725,3969562369,3801332234,4035489047,4269907996,3569255213,3669462566,3366754619,3332740144,2631065433,2463879762,2160117071,2395588676,2767645557,2868897406,3102011747,3069049960,202008497,33778362,270040487,504459436,875451293,975658646,675039627,641025152,2084704233,1917518562,1615861247,1851332852,1147550661,1248802510,1484005843,1451044056,933301370,967311729,733156972,632953703,260388950,25965917,328671808,496906059,1206477858,1239443753,1543208500,1441952575,2144161806,1908694277,1675577880,1842759443,3610369226,3644379585,3408119516,3307916247,4011190502,3776767469,4077384432,4245618683,2809771154,2842737049,3144396420,3043140495,2673705150,2438237621,2203032232,2370213795]);r.prototype={decryptBlock:function(e,t){var a,r=e.length,n=this.buffer,s=this.bufferPosition;for(a=0;s<16&&a<r;++a,++s)n[s]=e[a];if(s<16){this.bufferLength=s;return new Uint8Array([])}this.iv=n;this.buffer=new Uint8Array(16);this.bufferLength=0;this.decryptBlock=i;return this.decryptBlock(e.subarray(16),t)},encrypt:function(e,t){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[];t||(t=new Uint8Array(16));for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){for(i=0;i<16;++i)o[i]^=t[i];var h=a(o,this.key);t=h;l.push(h);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=t;if(0===l.length)return new Uint8Array([]);var u=16*l.length,f=new Uint8Array(u);for(r=0,i=0,n=l.length;r<n;++r,i+=16)f.set(l[r],i);return f}};return r}(),B=function(){function e(e){var t=new Uint8Array(240),a=1;t.set(e);for(var r=32,i=1;r<240;++i){if(r%32==16){s=n[s];o=n[o];c=n[c];l=n[l]}else if(r%32==0){var s=t[r-3],o=t[r-2],c=t[r-1],l=t[r-4];s=n[s];o=n[o];c=n[c];l=n[l];s^=a;(a<<=1)>=256&&(a=255&(27^a))}for(var h=0;h<4;++h){t[r]=s^=t[r-32];r++;t[r]=o^=t[r-32];r++;t[r]=c^=t[r-32];r++;t[r]=l^=t[r-32];r++}}return t}function t(e,t){var a=new Uint8Array(16);a.set(e);var r,i,n,o,c,h;for(i=0,n=224;i<16;++i,++n)a[i]^=t[n];for(r=13;r>=1;--r){o=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=o;o=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=o;a[2]=c;o=a[15];c=a[11];h=a[7];a[15]=a[3];a[11]=o;a[7]=c;a[3]=h;for(i=0;i<16;++i)a[i]=s[a[i]];for(i=0,n=16*r;i<16;++i,++n)a[i]^=t[n];for(i=0;i<16;i+=4){var u=l[a[i]],f=l[a[i+1]],d=l[a[i+2]],g=l[a[i+3]];o=u^f>>>8^f<<24^d>>>16^d<<16^g>>>24^g<<8;a[i]=o>>>24&255;a[i+1]=o>>16&255;a[i+2]=o>>8&255;a[i+3]=255&o}}o=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=o;o=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=o;a[2]=c;o=a[15];c=a[11];h=a[7];a[15]=a[3];a[11]=o;a[7]=c;a[3]=h;for(i=0;i<16;++i){a[i]=s[a[i]];a[i]^=t[i]}return a}function a(e,t){var a,r,i,s,l=new Uint8Array(16);l.set(e);for(h=0;h<16;++h)l[h]^=t[h];for(c=1;c<14;c++){for(h=0;h<16;++h)l[h]=n[l[h]];i=l[1];l[1]=l[5];l[5]=l[9];l[9]=l[13];l[13]=i;i=l[2];r=l[6];l[2]=l[10];l[6]=l[14];l[10]=i;l[14]=r;i=l[3];r=l[7];a=l[11];l[3]=l[15];l[7]=i;l[11]=r;l[15]=a;for(var h=0;h<16;h+=4){var u=l[h+0],f=l[h+1],d=l[h+2],g=l[h+3];a=u^f^d^g;l[h+0]^=a^o[u^f];l[h+1]^=a^o[f^d];l[h+2]^=a^o[d^g];l[h+3]^=a^o[g^u]}for(h=0,s=16*c;h<16;++h,++s)l[h]^=t[s]}for(h=0;h<16;++h)l[h]=n[l[h]];i=l[1];l[1]=l[5];l[5]=l[9];l[9]=l[13];l[13]=i;i=l[2];r=l[6];l[2]=l[10];l[6]=l[14];l[10]=i;l[14]=r;i=l[3];r=l[7];a=l[11];l[3]=l[15];l[7]=i;l[11]=r;l[15]=a;for(h=0,s=224;h<16;++h,++s)l[h]^=t[s];return l}function r(t){this.key=e(t);this.buffer=new Uint8Array(16);this.bufferPosition=0}function i(e,a){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[],h=this.iv;for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){var u=t(o,this.key);for(i=0;i<16;++i)u[i]^=h[i];h=o;l.push(u);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=h;if(0===l.length)return new Uint8Array([]);var f=16*l.length;if(a){var d=l[l.length-1],g=d[15];if(g<=16){for(r=15,n=16-g;r>=n;--r)if(d[r]!==g){g=0;break}f-=g;l[l.length-1]=d.subarray(0,16-g)}}var p=new Uint8Array(f);for(r=0,i=0,n=l.length;r<n;++r,i+=16)p.set(l[r],i);return p}for(var n=new Uint8Array([99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22]),s=new Uint8Array([82,9,106,213,48,54,165,56,191,64,163,158,129,243,215,251,124,227,57,130,155,47,255,135,52,142,67,68,196,222,233,203,84,123,148,50,166,194,35,61,238,76,149,11,66,250,195,78,8,46,161,102,40,217,36,178,118,91,162,73,109,139,209,37,114,248,246,100,134,104,152,22,212,164,92,204,93,101,182,146,108,112,72,80,253,237,185,218,94,21,70,87,167,141,157,132,144,216,171,0,140,188,211,10,247,228,88,5,184,179,69,6,208,44,30,143,202,63,15,2,193,175,189,3,1,19,138,107,58,145,17,65,79,103,220,234,151,242,207,206,240,180,230,115,150,172,116,34,231,173,53,133,226,249,55,232,28,117,223,110,71,241,26,113,29,41,197,137,111,183,98,14,170,24,190,27,252,86,62,75,198,210,121,32,154,219,192,254,120,205,90,244,31,221,168,51,136,7,199,49,177,18,16,89,39,128,236,95,96,81,127,169,25,181,74,13,45,229,122,159,147,201,156,239,160,224,59,77,174,42,245,176,200,235,187,60,131,83,153,97,23,43,4,126,186,119,214,38,225,105,20,99,85,33,12,125]),o=new Uint8Array(256),c=0;c<256;c++)o[c]=c<128?c<<1:c<<1^27;var l=new Uint32Array([0,235474187,470948374,303765277,941896748,908933415,607530554,708780849,1883793496,2118214995,1817866830,1649639237,1215061108,1181045119,1417561698,1517767529,3767586992,4003061179,4236429990,4069246893,3635733660,3602770327,3299278474,3400528769,2430122216,2664543715,2362090238,2193862645,2835123396,2801107407,3035535058,3135740889,3678124923,3576870512,3341394285,3374361702,3810496343,3977675356,4279080257,4043610186,2876494627,2776292904,3076639029,3110650942,2472011535,2640243204,2403728665,2169303058,1001089995,899835584,666464733,699432150,59727847,226906860,530400753,294930682,1273168787,1172967064,1475418501,1509430414,1942435775,2110667444,1876241833,1641816226,2910219766,2743034109,2976151520,3211623147,2505202138,2606453969,2302690252,2269728455,3711829422,3543599269,3240894392,3475313331,3843699074,3943906441,4178062228,4144047775,1306967366,1139781709,1374988112,1610459739,1975683434,2076935265,1775276924,1742315127,1034867998,866637845,566021896,800440835,92987698,193195065,429456164,395441711,1984812685,2017778566,1784663195,1683407248,1315562145,1080094634,1383856311,1551037884,101039829,135050206,437757123,337553864,1042385657,807962610,573804783,742039012,2531067453,2564033334,2328828971,2227573024,2935566865,2700099354,3001755655,3168937228,3868552805,3902563182,4203181171,4102977912,3736164937,3501741890,3265478751,3433712980,1106041591,1340463100,1576976609,1408749034,2043211483,2009195472,1708848333,1809054150,832877231,1068351396,766945465,599762354,159417987,126454664,361929877,463180190,2709260871,2943682380,3178106961,3009879386,2572697195,2538681184,2236228733,2336434550,3509871135,3745345300,3441850377,3274667266,3910161971,3877198648,4110568485,4211818798,2597806476,2497604743,2261089178,2295101073,2733856160,2902087851,3202437046,2968011453,3936291284,3835036895,4136440770,4169408201,3535486456,3702665459,3467192302,3231722213,2051518780,1951317047,1716890410,1750902305,1113818384,1282050075,1584504582,1350078989,168810852,67556463,371049330,404016761,841739592,1008918595,775550814,540080725,3969562369,3801332234,4035489047,4269907996,3569255213,3669462566,3366754619,3332740144,2631065433,2463879762,2160117071,2395588676,2767645557,2868897406,3102011747,3069049960,202008497,33778362,270040487,504459436,875451293,975658646,675039627,641025152,2084704233,1917518562,1615861247,1851332852,1147550661,1248802510,1484005843,1451044056,933301370,967311729,733156972,632953703,260388950,25965917,328671808,496906059,1206477858,1239443753,1543208500,1441952575,2144161806,1908694277,1675577880,1842759443,3610369226,3644379585,3408119516,3307916247,4011190502,3776767469,4077384432,4245618683,2809771154,2842737049,3144396420,3043140495,2673705150,2438237621,2203032232,2370213795]);r.prototype={decryptBlock:function(e,t,a){var r,n=e.length,s=this.buffer,o=this.bufferPosition;if(a)this.iv=a;else{for(r=0;o<16&&r<n;++r,++o)s[o]=e[r];if(o<16){this.bufferLength=o;return new Uint8Array([])}this.iv=s;e=e.subarray(16)}this.buffer=new Uint8Array(16);this.bufferLength=0;this.decryptBlock=i;return this.decryptBlock(e,t)},encrypt:function(e,t){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[];t||(t=new Uint8Array(16));for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){for(i=0;i<16;++i)o[i]^=t[i];var h=a(o,this.key);this.iv=h;l.push(h);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=t;if(0===l.length)return new Uint8Array([]);var u=16*l.length,f=new Uint8Array(u);for(r=0,i=0,n=l.length;r<n;++r,i+=16)f.set(l[r],i);return f}};return r}(),R=function(){function e(e,t){if(e.length!==t.length)return!1;for(var a=0;a<e.length;a++)if(e[a]!==t[a])return!1;return!0}function t(){}t.prototype={checkOwnerPassword:function(t,a,r,i){var n=new Uint8Array(t.length+56);n.set(t,0);n.set(a,t.length);n.set(r,t.length+a.length);return e(C(n,0,n.length),i)},checkUserPassword:function(t,a,r){var i=new Uint8Array(t.length+8);i.set(t,0);i.set(a,t.length);return e(C(i,0,i.length),r)},getOwnerKey:function(e,t,a,r){var i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);var n=C(i,0,i.length);return new B(n).decryptBlock(r,!1,new Uint8Array(16))},getUserKey:function(e,t,a){var r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);var i=C(r,0,r.length);return new B(i).decryptBlock(a,!1,new Uint8Array(16))}};return t}(),T=function(){function e(e,t){var a=new Uint8Array(e.length+t.length);a.set(e,0);a.set(t,e.length);return a}function t(t,a,r){for(var i=C(a,0,a.length).subarray(0,32),n=[0],s=0;s<64||n[n.length-1]>s-32;){var o=t.length+i.length+r.length,c=new Uint8Array(64*o),l=e(t,i);l=e(l,r);for(var h=0,u=0;h<64;h++,u+=o)c.set(l,u);n=new I(i.subarray(0,16)).encrypt(c,i.subarray(16,32));for(var f=0,d=0;d<16;d++){f*=1;f%=3;f+=(n[d]>>>0)%3;f%=3}0===f?i=C(n,0,n.length):1===f?i=S(n,0,n.length):2===f&&(i=x(n,0,n.length));s++}return i.subarray(0,32)}function a(){}function r(e,t){if(e.length!==t.length)return!1;for(var a=0;a<e.length;a++)if(e[a]!==t[a])return!1;return!0}a.prototype={hash:function(e,a,r){return t(e,a,r)},checkOwnerPassword:function(e,a,i,n){var s=new Uint8Array(e.length+56);s.set(e,0);s.set(a,e.length);s.set(i,e.length+a.length);return r(t(e,s,i),n)},checkUserPassword:function(e,a,i){var n=new Uint8Array(e.length+8);n.set(e,0);n.set(a,e.length);return r(t(e,n,[]),i)},getOwnerKey:function(e,a,r,i){var n=new Uint8Array(e.length+56);n.set(e,0);n.set(a,e.length);n.set(r,e.length+a.length);var s=t(e,n,r);return new B(s).decryptBlock(i,!1,new Uint8Array(16))},getUserKey:function(e,a,r){var i=new Uint8Array(e.length+8);i.set(e,0);i.set(a,e.length);var n=t(e,i,[]);return new B(n).decryptBlock(r,!1,new Uint8Array(16))}};return a}(),O=function(){function e(e,t){this.StringCipherConstructor=e;this.StreamCipherConstructor=t}e.prototype={createStream:function(e,t){var a=new this.StreamCipherConstructor;return new v(e,t,function(e,t){return a.decryptBlock(e,t)})},decryptString:function(e){var t=new this.StringCipherConstructor,a=d(e);a=t.decryptBlock(a,!0);return c(a)}};return e}(),P=function(){function e(e,t,a,r,i,n,s,o,c,l,h,u){if(t){var f=Math.min(127,t.length);t=t.subarray(0,f)}else t=[];var d;d=6===e?new T:new R;return d.checkUserPassword(t,o,s)?d.getUserKey(t,c,h):t.length&&d.checkOwnerPassword(t,r,n,a)?d.getOwnerKey(t,i,n,l):null}function t(e,t,a,r,i,n,s,o){var l,h,u=40+a.length+e.length,f=new Uint8Array(u),d=0;if(t){h=Math.min(32,t.length);for(;d<h;++d)f[d]=t[d]}l=0;for(;d<32;)f[d++]=c[l++];for(l=0,h=a.length;l<h;++l)f[d++]=a[l];f[d++]=255&i;f[d++]=i>>8&255;f[d++]=i>>16&255;f[d++]=i>>>24&255;for(l=0,h=e.length;l<h;++l)f[d++]=e[l];if(n>=4&&!o){f[d++]=255;f[d++]=255;f[d++]=255;f[d++]=255}var g=k(f,0,d),p=s>>3;if(n>=3)for(l=0;l<50;++l)g=k(g,0,p);var m,b,v=g.subarray(0,p);if(n>=3){for(d=0;d<32;++d)f[d]=c[d];for(l=0,h=e.length;l<h;++l)f[d++]=e[l];m=new y(v);b=m.encryptBlock(k(f,0,d));h=v.length;var w,C=new Uint8Array(h);for(l=1;l<=19;++l){for(w=0;w<h;++w)C[w]=v[w]^l;m=new y(C);b=m.encryptBlock(b)}for(l=0,h=b.length;l<h;++l)if(r[l]!==b[l])return null}else{m=new y(v);b=m.encryptBlock(c);for(l=0,h=b.length;l<h;++l)if(r[l]!==b[l])return null}return v}function a(e,t,a,r){var i,n,s=new Uint8Array(32),o=0;n=Math.min(32,e.length);for(;o<n;++o)s[o]=e[o];i=0;for(;o<32;)s[o++]=c[i++];var l=k(s,0,o),h=r>>3;if(a>=3)for(i=0;i<50;++i)l=k(l,0,l.length);var u,f;if(a>=3){f=t;var d,g=new Uint8Array(h);for(i=19;i>=0;i--){for(d=0;d<h;++d)g[d]=l[d]^i;u=new y(g);f=u.encryptBlock(f)}}else{u=new y(l.subarray(0,h));f=u.encryptBlock(t)}return f}function r(r,i,n){var c=r.get("Filter");m(c,"Standard")||h("unknown encryption method");this.dict=r;var u=r.get("V");(!f(u)||1!==u&&2!==u&&4!==u&&5!==u)&&h("unsupported encryption algorithm");this.algorithm=u;var p=r.get("Length");if(!p)if(u<=3)p=40;else{var y=r.get("CF"),k=r.get("StmF");if(b(y)&&m(k)){y.suppressEncryption=!0;var w=y.get(k.name);p=w&&w.get("Length")||128;p<40&&(p<<=3)}}(!f(p)||p<40||p%8!=0)&&h("invalid key length");var C=d(r.get("O")).subarray(0,32),x=d(r.get("U")).subarray(0,32),S=r.get("P"),A=r.get("R"),I=(4===u||5===u)&&!1!==r.get("EncryptMetadata");this.encryptMetadata=I;var B,R=d(i);if(n){if(6===A)try{n=g(n)}catch(e){l("CipherTransformFactory: Unable to convert UTF8 encoded password.")}B=d(n)}var T;if(5!==u)T=t(R,B,C,x,S,A,p,I);else{T=e(A,B,C,d(r.get("O")).subarray(32,40),d(r.get("O")).subarray(40,48),d(r.get("U")).subarray(0,48),x,d(r.get("U")).subarray(32,40),d(r.get("U")).subarray(40,48),d(r.get("OE")),d(r.get("UE")),d(r.get("Perms")))}if(!T&&!n)throw new s("No password given",o.NEED_PASSWORD);if(!T&&n){T=t(R,a(B,C,A,p),C,x,S,A,p,I)}if(!T)throw new s("Incorrect Password",o.INCORRECT_PASSWORD);this.encryptionKey=T;if(u>=4){var O=r.get("CF");b(O)&&(O.suppressEncryption=!0);this.cf=O;this.stmf=r.get("StmF")||v;this.strf=r.get("StrF")||v;this.eff=r.get("EFF")||this.stmf}}function i(e,t,a,r){var i,n,s=new Uint8Array(a.length+9);for(i=0,n=a.length;i<n;++i)s[i]=a[i];s[i++]=255&e;s[i++]=e>>8&255;s[i++]=e>>16&255;s[i++]=255&t;s[i++]=t>>8&255;if(r){s[i++]=115;s[i++]=65;s[i++]=108;s[i++]=84}return k(s,0,i).subarray(0,Math.min(a.length+5,16))}function n(e,t,a,r,n){u(m(t),"Invalid crypt filter name.");var s,o=e.get(t.name);null!==o&&void 0!==o&&(s=o.get("CFM"));if(!s||"None"===s.name)return function(){return new A};if("V2"===s.name)return function(){return new y(i(a,r,n,!1))};if("AESV2"===s.name)return function(){return new I(i(a,r,n,!0))};if("AESV3"===s.name)return function(){return new B(n)};h("Unknown crypto method")}var c=new Uint8Array([40,191,78,94,78,117,138,65,100,0,78,86,255,250,1,8,46,46,0,182,208,104,62,128,47,12,169,254,100,83,105,122]),v=p.get("Identity");r.prototype={createCipherTransform:function(e,t){if(4===this.algorithm||5===this.algorithm)return new O(n(this.cf,this.stmf,e,t,this.encryptionKey),n(this.cf,this.strf,e,t,this.encryptionKey));var a=i(e,t,this.encryptionKey,!1),r=function(){return new y(a)};return new O(r,r)}};return r}();t.AES128Cipher=I;t.AES256Cipher=B;t.ARCFourCipher=y;t.CipherTransformFactory=P;t.PDF17=R;t.PDF20=T;t.calculateMD5=k;t.calculateSHA256=C;t.calculateSHA384=S;t.calculateSHA512=x},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(5),o=a(27),c=a(3),l=a(31),h=a(26),u=a(6),f=a(32),d=a(23),g=a(30),p=a(21),m=a(4),b=a(17),v=a(18),y=a(7),k=r.FONT_IDENTITY_MATRIX,w=r.IDENTITY_MATRIX,C=r.UNSUPPORTED_FEATURES,x=r.ImageKind,S=r.OPS,A=r.TextRenderingMode,I=r.CMapCompressionType,B=r.Util,R=r.assert,T=r.createPromiseCapability,O=r.error,P=r.info,M=r.isArray,E=r.isNum,L=r.isString,D=r.getLookupTableFactory,F=r.warn,q=i.Dict,U=i.Name,N=i.isEOF,j=i.isCmd,_=i.isDict,z=i.isName,H=i.isRef,G=i.isStream,X=n.DecodeStream,V=n.JpegStream,W=n.Stream,K=s.Lexer,Y=s.Parser,J=o.PDFImage,Z=c.ColorSpace,Q=l.MurmurHash3_64,$=h.ErrorFont,ee=h.FontFlags,te=h.Font,ae=h.IdentityToUnicodeMap,re=h.ToUnicodeMap,ie=h.getFontType,ne=u.isPDFFunction,se=u.PDFFunction,oe=f.Pattern,ce=f.getTilingPatternIR,le=d.CMapFactory,he=d.IdentityCMap,ue=g.getMetrics,fe=p.bidi,de=m.WinAnsiEncoding,ge=m.StandardEncoding,pe=m.MacRomanEncoding,me=m.SymbolSetEncoding,be=m.ZapfDingbatsEncoding,ve=m.getEncoding,ye=b.getStdFontMap,ke=b.getSerifFonts,we=b.getSymbolsFonts,Ce=v.getNormalizedUnicodes,xe=v.reverseIfRtl,Se=v.getUnicodeForGlyph,Ae=y.getGlyphsUnicode,Ie=function(){function e(e,t,a,r){this.xref=e;this.resources=t;this.handler=a;this.forceDataSchema=r}function t(e,t,a,i,n,s,o,c){this.pdfManager=e;this.xref=t;this.handler=a;this.pageIndex=i;this.idFactory=n;this.fontCache=s;this.builtInCMapCache=o;this.options=c||r;this.fetchBuiltInCMap=function(e){var t=o[e];return t?Promise.resolve(t):a.sendWithPromise("FetchBuiltInCMap",{name:e}).then(function(t){t.compressionType!==I.NONE&&(o[e]=t);return t})}}function a(){this.reset()}var r={forceDataSchema:!1,maxImageSize:-1,disableFontFace:!1,disableNativeImageDecoder:!1};e.prototype={canDecode:function(t){return t instanceof V&&e.isDecodable(t,this.xref,this.resources)},decode:function(e){var t=e.dict,a=t.get("ColorSpace","CS");a=Z.parse(a,this.xref,this.resources);var r=a.numComps;return this.handler.sendWithPromise("JpegDecode",[e.getIR(this.forceDataSchema),r]).then(function(t){var a=t.data;return new W(a,0,a.length,e.dict)})}};e.isSupported=function(e,t,a){var r=e.dict;if(r.has("DecodeParms")||r.has("DP"))return!1;var i=Z.parse(r.get("ColorSpace","CS"),t,a);return("DeviceGray"===i.name||"DeviceRGB"===i.name)&&i.isDefaultDecode(r.getArray("Decode","D"))};e.isDecodable=function(e,t,a){var r=e.dict;if(r.has("DecodeParms")||r.has("DP"))return!1;var i=Z.parse(r.get("ColorSpace","CS"),t,a);return(1===i.numComps||3===i.numComps)&&i.isDefaultDecode(r.getArray("Decode","D"))};a.prototype={check:function(){if(++this.checked<100)return!1;this.checked=0;return this.endTime<=Date.now()},reset:function(){this.endTime=Date.now()+20;this.checked=0}};var i=Promise.resolve();t.prototype={hasBlendModes:function(e){if(!_(e))return!1;var t=Object.create(null);e.objId&&(t[e.objId]=!0);for(var a=[e],r=this.xref;a.length;){var i,n,s,o=a.shift(),c=o.get("ExtGState");if(_(c)){var l=c.getKeys();for(n=0,s=l.length;n<s;n++){i=l[n];var h=c.get(i),u=h.get("BM");if(z(u)&&"Normal"!==u.name)return!0}}var f=o.get("XObject");if(_(f)){var d=f.getKeys();for(n=0,s=d.length;n<s;n++){i=d[n];var g=f.getRaw(i);if(H(g)){if(t[g.toString()])continue;g=r.fetch(g)}if(G(g)){if(g.dict.objId){if(t[g.dict.objId])continue;t[g.dict.objId]=!0}var p=g.dict.get("Resources");if(_(p)&&(!p.objId||!t[p.objId])){a.push(p);p.objId&&(t[p.objId]=!0)}}}}}return!1},buildFormXObject:function(e,t,a,r,i,n){var s=t.dict.getArray("Matrix"),o=t.dict.getArray("BBox"),c=t.dict.get("Group");if(c){var l,h={matrix:s,bbox:o,smask:a,isolated:!1,knockout:!1},u=c.get("S");if(z(u,"Transparency")){h.isolated=c.get("I")||!1;h.knockout=c.get("K")||!1;l=c.has("CS")?Z.parse(c.get("CS"),this.xref,e):null}if(a&&a.backdrop){l=l||Z.singletons.rgb;a.backdrop=l.getRgb(a.backdrop,0)}r.addOp(S.beginGroup,[h])}r.addOp(S.paintFormXObjectBegin,[s,o]);return this.getOperatorList(t,i,t.dict.get("Resources")||e,r,n).then(function(){r.addOp(S.paintFormXObjectEnd,[]);c&&r.addOp(S.endGroup,[h])})},buildPaintImageXObject:function(t,a,r,i,n,s){var o=this,c=a.dict,l=c.get("Width","W"),h=c.get("Height","H");if(l&&E(l)&&h&&E(h)){var u=this.options.maxImageSize;if(-1!==u&&l*h>u)F("Image exceeded maximum allowed size and was removed.");else{var f,d,g=c.get("ImageMask","IM")||!1;if(g){var p=c.get("Width","W"),m=c.get("Height","H"),b=p+7>>3,v=a.getBytes(b*m),y=c.getArray("Decode","D"),k=!!y&&y[0]>0;f=J.createMask(v,p,m,a instanceof X,k);f.cached=!0;d=[f];i.addOp(S.paintImageMaskXObject,d);n&&(s[n]={fn:S.paintImageMaskXObject,args:d})}else{var w=c.get("SMask","SM")||!1,C=c.get("Mask")||!1;if(!r||w||C||a instanceof V||!(l+h<200)){var x=!this.options.disableNativeImageDecoder,A="img_"+this.idFactory.createObjId();i.addDependency(A);d=[A,l,h];if(x&&!w&&!C&&a instanceof V&&e.isSupported(a,this.xref,t)){i.addOp(S.paintJpegXObject,d);this.handler.send("obj",[A,this.pageIndex,"JpegStream",a.getIR(this.options.forceDataSchema)])}else{var I=null;x&&(a instanceof V||C instanceof V||w instanceof V)&&(I=new e(o.xref,t,o.handler,o.options.forceDataSchema));J.buildImage(o.handler,o.xref,t,a,r,I).then(function(e){var t=e.createImageData(!1);o.handler.send("obj",[A,o.pageIndex,"Image",t],[t.data.buffer])}).then(void 0,function(e){F("Unable to decode image: "+e);o.handler.send("obj",[A,o.pageIndex,"Image",null])});i.addOp(S.paintImageXObject,d);n&&(s[n]={fn:S.paintImageXObject,args:d})}}else{f=new J(this.xref,t,a,r,null,null).createImageData(!0);i.addOp(S.paintInlineImageXObject,[f])}}}}else F("Image dimensions are missing, or not numbers.")},handleSMask:function(e,t,a,r,i){var n=e.get("G"),s={subtype:e.get("S").name,backdrop:e.get("BC")},o=e.get("TR");if(ne(o)){for(var c=se.parse(this.xref,o),l=new Uint8Array(256),h=new Float32Array(1),u=0;u<256;u++){h[0]=u/255;c(h,0,h,0);l[u]=255*h[0]|0}s.transferMap=l}return this.buildFormXObject(t,n,s,a,r,i.state.clone())},handleTilingType:function(e,t,a,r,i,n,s){var o=new Re,c=[i.get("Resources"),a],l=q.merge(this.xref,c);return this.getOperatorList(r,s,l,o).then(function(){n.addDependencies(o.dependencies);n.addOp(e,ce({fnArray:o.fnArray,argsArray:o.argsArray},i,t))})},handleSetFont:function(e,t,a,r,i,n){var s;if(t){t=t.slice();s=t[0].name}var o=this;return this.loadFont(s,a,e).then(function(t){return t.font.isType3Font?t.loadType3Data(o,e,r,i).then(function(){return t},function(e){o.handler.send("UnsupportedFeature",{featureId:C.font});return new Be("g_font_error",new $("Type3 font load error: "+e),t.font)}):t}).then(function(e){n.font=e.font;e.send(o.handler);return e.loadedName})},handleText:function(e,t){var a=t.font,r=a.charsToGlyphs(e),i=!!(t.textRenderingMode&A.ADD_TO_PATH_FLAG);if(a.data&&(i||this.options.disableFontFace))for(var n=function(e){if(!a.renderer.hasBuiltPath(e)){var t=a.renderer.getPathJs(e);this.handler.send("commonobj",[a.loadedName+"_path_"+e,"FontPath",t])}}.bind(this),s=0,o=r.length;s<o;s++){var c=r[s];n(c.fontChar);var l=c.accent;l&&l.fontChar&&n(l.fontChar)}return r},setGState:function(e,t,a,r,i){for(var n=[],s=t.getKeys(),o=this,c=Promise.resolve(),l=0,h=s.length;l<h;l++){var u=s[l],f=t.get(u);switch(u){case"Type":break;case"LW":case"LC":case"LJ":case"ML":case"D":case"RI":case"FL":case"CA":case"ca":n.push([u,f]);break;case"Font":c=c.then(function(){return o.handleSetFont(e,null,f[0],a,r,i.state).then(function(e){a.addDependency(e);n.push([u,[e,f[1]]])})});break;case"BM":n.push([u,f]);break;case"SMask":if(z(f,"None")){n.push([u,!1]);break}if(_(f)){c=c.then(function(t){return o.handleSMask(t,e,a,r,i)}.bind(this,f));n.push([u,!0])}else F("Unsupported SMask type");break;case"OP":case"op":case"OPM":case"BG":case"BG2":case"UCR":case"UCR2":case"TR":case"TR2":case"HT":case"SM":case"SA":case"AIS":case"TK":P("graphic state operator "+u);break;default:P("Unknown graphic state operator "+u)}}return c.then(function(){n.length>0&&a.addOp(S.setGState,[n])})},loadFont:function(e,t,a){function r(){return Promise.resolve(new Be("g_font_error",new $("Font "+e+" is not available"),t))}var i,n=this.xref;if(t){R(H(t));i=t}else{var s=a.get("Font");if(!s){F("fontRes not available");return r()}i=s.getRaw(e)}if(!i){F("fontRef not available");return r()}if(this.fontCache.has(i))return this.fontCache.get(i);t=n.fetchIfRef(i);if(!_(t))return r();if(t.translated)return t.translated;var o,c=T(),l=this.preEvaluateFont(t),h=l.descriptor,u=H(i);u&&(o=i.toString());if(_(h)){h.fontAliases||(h.fontAliases=Object.create(null));var f=h.fontAliases,d=l.hash;if(f[d]){var g=f[d].aliasRef;if(u&&g&&this.fontCache.has(g)){this.fontCache.putAlias(i,g);return this.fontCache.get(i)}}else f[d]={fontID:te.getFontID()};u&&(f[d].aliasRef=i);o=f[d].fontID}if(u)this.fontCache.put(i,c.promise);else{o||(o=this.idFactory.createObjId());this.fontCache.put("id_"+o,c.promise)}R(o,'The "fontID" must be defined.');t.loadedName="g_"+this.pdfManager.docId+"_f"+o;t.translated=c.promise;var p;try{p=this.translateFont(l)}catch(e){p=Promise.reject(e)}var m=this;p.then(function(e){if(void 0!==e.fontType){n.stats.fontTypes[e.fontType]=!0}c.resolve(new Be(t.loadedName,e,t))},function(e){m.handler.send("UnsupportedFeature",{featureId:C.font});try{var a=l.descriptor,r=a&&a.get("FontFile3"),i=r&&r.get("Subtype"),s=ie(l.type,i&&i.name);n.stats.fontTypes[s]=!0}catch(e){}c.resolve(new Be(t.loadedName,new $(e instanceof Error?e.message:e),t))});return c.promise},buildPath:function(e,t,a){var r=e.length-1;a||(a=[]);if(r<0||e.fnArray[r]!==S.constructPath)e.addOp(S.constructPath,[[t],a]);else{var i=e.argsArray[r];i[0].push(t);Array.prototype.push.apply(i[1],a)}},handleColorN:function(e,t,a,r,i,n,s){var o,c=a[a.length-1];if(z(c)&&(o=i.get(c.name))){var l=G(o)?o.dict:o,h=l.get("PatternType");if(1===h){var u=r.base?r.base.getRgb(a,0):null;return this.handleTilingType(t,u,n,o,l,e,s)}if(2===h){var f=l.get("Shading"),d=l.getArray("Matrix");o=oe.parseShading(f,d,this.xref,n,this.handler);e.addOp(t,o.getIR());return Promise.resolve()}return Promise.reject("Unknown PatternType: "+h)}e.addOp(t,a);return Promise.resolve()},getOperatorList:function(e,t,r,n,s){var o=this,c=this.xref,l=Object.create(null);R(n);r=r||q.empty;var h=r.get("XObject")||q.empty,u=r.get("Pattern")||q.empty,f=new Te(s||new Pe),d=new Me(e,c,f),g=new a;return new Promise(function e(a,s){var p=function(t){t.then(function(){try{e(a,s)}catch(e){s(e)}},s)};t.ensureNotTerminated();g.reset();for(var m,b,v,y,k={};!(m=g.check());){k.args=null;if(!d.read(k))break;var w=k.args,C=k.fn;switch(0|C){case S.paintXObject:if(w[0].code)break;var x=w[0].name;if(!x){F("XObject must be referred to by name.");continue}if(void 0!==l[x]){n.addOp(l[x].fn,l[x].args);w=null;continue}var A=h.get(x);if(A){R(G(A),"XObject should be a stream");var I=A.dict.get("Subtype");R(z(I),"XObject should have a Name subtype");if("Form"===I.name){f.save();p(o.buildFormXObject(r,A,null,n,t,f.state.clone()).then(function(){f.restore()}));return}if("Image"===I.name){o.buildPaintImageXObject(r,A,!1,n,x,l);w=null;continue}if("PS"===I.name){P("Ignored XObject subtype PS");continue}O("Unhandled XObject subtype "+I.name)}break;case S.setFont:var B=w[1];p(o.handleSetFont(r,w,null,n,t,f.state).then(function(e){n.addDependency(e);n.addOp(S.setFont,[e,B])}));return;case S.endInlineImage: +var T=w[0].cacheKey;if(T){var M=l[T];if(void 0!==M){n.addOp(M.fn,M.args);w=null;continue}}o.buildPaintImageXObject(r,w[0],!0,n,T,l);w=null;continue;case S.showText:w[0]=o.handleText(w[0],f.state);break;case S.showSpacedText:var D=w[0],U=[],N=D.length,j=f.state;for(b=0;b<N;++b){var H=D[b];L(H)?Array.prototype.push.apply(U,o.handleText(H,j)):E(H)&&U.push(H)}w[0]=U;C=S.showText;break;case S.nextLineShowText:n.addOp(S.nextLine);w[0]=o.handleText(w[0],f.state);C=S.showText;break;case S.nextLineSetSpacingShowText:n.addOp(S.nextLine);n.addOp(S.setWordSpacing,[w.shift()]);n.addOp(S.setCharSpacing,[w.shift()]);w[0]=o.handleText(w[0],f.state);C=S.showText;break;case S.setTextRenderingMode:f.state.textRenderingMode=w[0];break;case S.setFillColorSpace:f.state.fillColorSpace=Z.parse(w[0],c,r);continue;case S.setStrokeColorSpace:f.state.strokeColorSpace=Z.parse(w[0],c,r);continue;case S.setFillColor:y=f.state.fillColorSpace;w=y.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeColor:y=f.state.strokeColorSpace;w=y.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillGray:f.state.fillColorSpace=Z.singletons.gray;w=Z.singletons.gray.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeGray:f.state.strokeColorSpace=Z.singletons.gray;w=Z.singletons.gray.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillCMYKColor:f.state.fillColorSpace=Z.singletons.cmyk;w=Z.singletons.cmyk.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeCMYKColor:f.state.strokeColorSpace=Z.singletons.cmyk;w=Z.singletons.cmyk.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillRGBColor:f.state.fillColorSpace=Z.singletons.rgb;w=Z.singletons.rgb.getRgb(w,0);break;case S.setStrokeRGBColor:f.state.strokeColorSpace=Z.singletons.rgb;w=Z.singletons.rgb.getRgb(w,0);break;case S.setFillColorN:y=f.state.fillColorSpace;if("Pattern"===y.name){p(o.handleColorN(n,S.setFillColorN,w,y,u,r,t));return}w=y.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeColorN:y=f.state.strokeColorSpace;if("Pattern"===y.name){p(o.handleColorN(n,S.setStrokeColorN,w,y,u,r,t));return}w=y.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.shadingFill:var X=r.get("Shading");R(X,"No shading resource found");var V=X.get(w[0].name);R(V,"No shading object found");w=[oe.parseShading(V,null,c,r,o.handler).getIR()];C=S.shadingFill;break;case S.setGState:var W=w[0],K=r.get("ExtGState");if(!_(K)||!K.has(W.name))break;var Y=K.get(W.name);p(o.setGState(r,Y,n,t,f));return;case S.moveTo:case S.lineTo:case S.curveTo:case S.curveTo2:case S.curveTo3:case S.closePath:case S.rectangle:o.buildPath(n,C,w);continue;case S.markPoint:case S.markPointProps:case S.beginMarkedContent:case S.beginMarkedContentProps:case S.endMarkedContent:case S.beginCompat:case S.endCompat:continue;default:if(null!==w){for(b=0,v=w.length;b<v&&!(w[b]instanceof q);b++);if(b<v){F("getOperatorList - ignoring operator: "+C);continue}}}n.addOp(C,w)}if(m)p(i);else{for(b=0,v=d.savedStatesDepth;b<v;b++)n.addOp(S.restore,[]);a()}})},getTextContent:function(e,t,r,n,s,o){function c(){if(b.initialized)return b;var e=I.font;e.loadedName in m.styles||(m.styles[e.loadedName]={fontFamily:e.fallbackName,ascent:e.ascent,descent:e.descent,vertical:e.vertical});b.fontName=e.loadedName;var t=[I.fontSize*I.textHScale,0,0,I.fontSize,0,I.textRise];if(e.isType3Font&&I.fontMatrix!==k&&1===I.fontSize){var a=e.bbox[3]-e.bbox[1];if(a>0){a*=I.fontMatrix[3];t[3]*=a}}var r=B.transform(I.ctm,B.transform(I.textMatrix,t));b.transform=r;if(e.vertical){b.width=Math.sqrt(r[0]*r[0]+r[1]*r[1]);b.height=0;b.vertical=!0}else{b.width=0;b.height=Math.sqrt(r[2]*r[2]+r[3]*r[3]);b.vertical=!1}var i=I.textLineMatrix[0],n=I.textLineMatrix[1],s=Math.sqrt(i*i+n*n);i=I.ctm[0];n=I.ctm[1];var o=Math.sqrt(i*i+n*n);b.textAdvanceScale=o*s;b.lastAdvanceWidth=0;b.lastAdvanceHeight=0;var c=e.spaceWidth/1e3*I.fontSize;if(c){b.spaceWidth=c;b.fakeSpaceMin=c*v;b.fakeMultiSpaceMin=c*y;b.fakeMultiSpaceMax=c*C;b.textRunBreakAllowed=!e.isMonospace}else{b.spaceWidth=0;b.fakeSpaceMin=1/0;b.fakeMultiSpaceMin=1/0;b.fakeMultiSpaceMax=0;b.textRunBreakAllowed=!1}b.initialized=!0;return b}function l(e){for(var t,a=0,r=e.length;a<r&&(t=e.charCodeAt(a))>=32&&t<=127;)a++;return a<r?e.replace(p," "):e}function h(e){var t=e.str.join(""),a=fe(t,-1,e.vertical);return{str:s?l(a.str):a.str,dir:a.dir,width:e.width,height:e.height,transform:e.transform,fontName:e.fontName}}function u(e,t){return x.loadFont(e,t,r).then(function(e){I.font=e.font;I.fontMatrix=e.font.fontMatrix||k})}function f(e){for(var t=I.font,a=c(),r=0,i=0,n=t.charsToGlyphs(e),s=0;s<n.length;s++){var o=n[s],l=null;l=t.vertical&&o.vmetric?o.vmetric[0]:o.width;var h=o.unicode,u=Ce();void 0!==u[h]&&(h=u[h]);h=xe(h);var f=I.charSpacing;if(o.isSpace){var g=I.wordSpacing;f+=g;g>0&&d(g,a.str)}var p=0,m=0;if(t.vertical){m=l*I.fontMatrix[0]*I.fontSize+f;i+=m}else{p=(l*I.fontMatrix[0]*I.fontSize+f)*I.textHScale;r+=p}I.translateTextMatrix(p,m);a.str.push(h)}if(t.vertical){a.lastAdvanceHeight=i;a.height+=Math.abs(i)}else{a.lastAdvanceWidth=r;a.width+=r}return a}function d(e,t){if(!(e<b.fakeSpaceMin))if(e<b.fakeMultiSpaceMin)t.push(" ");else for(var a=Math.round(e/b.spaceWidth);a-- >0;)t.push(" ")}function g(){if(b.initialized){b.width*=b.textAdvanceScale;b.height*=b.textAdvanceScale;m.items.push(h(b));b.initialized=!1;b.str.length=0}}n=n||new Te(new Oe);var p=/\s/g,m={items:[],styles:Object.create(null)},b={initialized:!1,str:[],width:0,height:0,vertical:!1,lastAdvanceWidth:0,lastAdvanceHeight:0,textAdvanceScale:0,spaceWidth:0,fakeSpaceMin:1/0,fakeMultiSpaceMin:1/0,fakeMultiSpaceMax:-0,textRunBreakAllowed:!1,transform:null,fontName:null},v=.3,y=1.5,C=4,x=this,A=this.xref;r=A.fetchIfRef(r)||q.empty;var I,T=null,O=Object.create(null),P=new Me(e,A,n),L=new a;return new Promise(function e(a,l){var h=function(t){t.then(function(){try{e(a,l)}catch(e){l(e)}},l)};t.ensureNotTerminated();L.reset();for(var p,v={},y=[];!(p=L.check());){y.length=0;v.args=y;if(!P.read(v))break;I=n.state;var k=v.fn;y=v.args;var C,A;switch(0|k){case S.setFont:var D=y[0].name,F=y[1];if(I.font&&D===I.fontName&&F===I.fontSize)break;g();I.fontName=D;I.fontSize=F;h(u(D,null));return;case S.setTextRise:g();I.textRise=y[0];break;case S.setHScale:g();I.textHScale=y[0]/100;break;case S.setLeading:g();I.leading=y[0];break;case S.moveText:var U=!!I.font&&0===(I.font.vertical?y[0]:y[1]);C=y[0]-y[1];if(o&&U&&b.initialized&&C>0&&C<=b.fakeMultiSpaceMax){I.translateTextLineMatrix(y[0],y[1]);b.width+=y[0]-b.lastAdvanceWidth;b.height+=y[1]-b.lastAdvanceHeight;A=y[0]-b.lastAdvanceWidth-(y[1]-b.lastAdvanceHeight);d(A,b.str);break}g();I.translateTextLineMatrix(y[0],y[1]);I.textMatrix=I.textLineMatrix.slice();break;case S.setLeadingMoveText:g();I.leading=-y[1];I.translateTextLineMatrix(y[0],y[1]);I.textMatrix=I.textLineMatrix.slice();break;case S.nextLine:g();I.carriageReturn();break;case S.setTextMatrix:C=I.calcTextLineMatrixAdvance(y[0],y[1],y[2],y[3],y[4],y[5]);if(o&&null!==C&&b.initialized&&C.value>0&&C.value<=b.fakeMultiSpaceMax){I.translateTextLineMatrix(C.width,C.height);b.width+=C.width-b.lastAdvanceWidth;b.height+=C.height-b.lastAdvanceHeight;A=C.width-b.lastAdvanceWidth-(C.height-b.lastAdvanceHeight);d(A,b.str);break}g();I.setTextMatrix(y[0],y[1],y[2],y[3],y[4],y[5]);I.setTextLineMatrix(y[0],y[1],y[2],y[3],y[4],y[5]);break;case S.setCharSpacing:I.charSpacing=y[0];break;case S.setWordSpacing:I.wordSpacing=y[0];break;case S.beginText:g();I.textMatrix=w.slice();I.textLineMatrix=w.slice();break;case S.showSpacedText:for(var N,j=y[0],H=0,X=j.length;H<X;H++)if("string"==typeof j[H])f(j[H]);else if(E(j[H])){c();C=j[H]*I.fontSize/1e3;var V=!1;if(I.font.vertical){N=C;I.translateTextMatrix(0,N);V=b.textRunBreakAllowed&&C>b.fakeMultiSpaceMax;V||(b.height+=N)}else{C=-C;N=C*I.textHScale;I.translateTextMatrix(N,0);V=b.textRunBreakAllowed&&C>b.fakeMultiSpaceMax;V||(b.width+=N)}V?g():C>0&&d(C,b.str)}break;case S.showText:f(y[0]);break;case S.nextLineShowText:g();I.carriageReturn();f(y[0]);break;case S.nextLineSetSpacingShowText:g();I.wordSpacing=y[0];I.charSpacing=y[1];I.carriageReturn();f(y[2]);break;case S.paintXObject:g();if(y[0].code)break;T||(T=r.get("XObject")||q.empty);var W=y[0].name;if(O.key===W){if(O.texts){B.appendToArray(m.items,O.texts.items);B.extendObj(m.styles,O.texts.styles)}break}var K=T.get(W);if(!K)break;R(G(K),"XObject should be a stream");var Y=K.dict.get("Subtype");R(z(Y),"XObject should have a Name subtype");if("Form"!==Y.name){O.key=W;O.texts=null;break}n.save();var J=K.dict.getArray("Matrix");M(J)&&6===J.length&&n.transform(J);h(x.getTextContent(K,t,K.dict.get("Resources")||r,n,s,o).then(function(e){B.appendToArray(m.items,e.items);B.extendObj(m.styles,e.styles);n.restore();O.key=W;O.texts=e}));return;case S.setGState:g();var Z=y[0],Q=r.get("ExtGState");if(!_(Q)||!z(Z))break;var $=Q.get(Z.name);if(!_($))break;var ee=$.get("Font");if(ee){I.fontName=null;I.fontSize=ee[1];h(u(null,ee[0]));return}}}if(p)h(i);else{g();a(m)}})},extractDataStructures:function(e,t,a){var r=this.xref,i=e.get("ToUnicode")||t.get("ToUnicode"),n=i?this.readToUnicode(i):Promise.resolve(void 0);if(a.composite){var s=e.get("CIDSystemInfo");_(s)&&(a.cidSystemInfo={registry:s.get("Registry"),ordering:s.get("Ordering"),supplement:s.get("Supplement")});var o=e.get("CIDToGIDMap");G(o)&&(a.cidToGidMap=this.readCidToGidMap(o))}var c,l=[],h=null;if(e.has("Encoding")){c=e.get("Encoding");if(_(c)){h=c.get("BaseEncoding");h=z(h)?h.name:null;if(c.has("Differences"))for(var u=c.get("Differences"),f=0,d=0,g=u.length;d<g;d++){var p=r.fetchIfRef(u[d]);E(p)?f=p:z(p)?l[f++]=p.name:O("Invalid entry in 'Differences' array: "+p)}}else z(c)?h=c.name:O("Encoding is not a Name nor a Dict");"MacRomanEncoding"!==h&&"MacExpertEncoding"!==h&&"WinAnsiEncoding"!==h&&(h=null)}if(h)a.defaultEncoding=ve(h).slice();else{var m=!!(a.flags&ee.Symbolic),b=!!(a.flags&ee.Nonsymbolic);c=ge;"TrueType"!==a.type||b||(c=de);if(m){c=pe;a.file||(/Symbol/i.test(a.name)?c=me:/Dingbats/i.test(a.name)&&(c=be))}a.defaultEncoding=c}a.differences=l;a.baseEncodingName=h;a.hasEncoding=!!h||l.length>0;a.dict=e;return n.then(function(e){a.toUnicode=e;return this.buildToUnicode(a)}.bind(this)).then(function(e){a.toUnicode=e;return a})},buildToUnicode:function(e){e.hasIncludedToUnicodeMap=!!e.toUnicode&&e.toUnicode.length>0;if(e.hasIncludedToUnicodeMap)return Promise.resolve(e.toUnicode);var t,a,r;if(!e.composite){t=[];var i=e.defaultEncoding.slice(),n=e.baseEncodingName,s=e.differences;for(a in s){r=s[a];".notdef"!==r&&(i[a]=r)}var o=Ae();for(a in i){r=i[a];if(""!==r)if(void 0!==o[r])t[a]=String.fromCharCode(o[r]);else{var c=0;switch(r[0]){case"G":3===r.length&&(c=parseInt(r.substr(1),16));break;case"g":5===r.length&&(c=parseInt(r.substr(1),16));break;case"C":case"c":r.length>=3&&(c=+r.substr(1));break;default:var l=Se(r,o);-1!==l&&(c=l)}if(c){if(n&&c===+a){var h=ve(n);if(h&&(r=h[a])){t[a]=String.fromCharCode(o[r]);continue}}t[a]=String.fromCharCode(c)}}}return Promise.resolve(new re(t))}if(e.composite&&(e.cMap.builtInCMap&&!(e.cMap instanceof he)||"Adobe"===e.cidSystemInfo.registry&&("GB1"===e.cidSystemInfo.ordering||"CNS1"===e.cidSystemInfo.ordering||"Japan1"===e.cidSystemInfo.ordering||"Korea1"===e.cidSystemInfo.ordering))){var u=e.cidSystemInfo.registry,f=e.cidSystemInfo.ordering,d=U.get(u+"-"+f+"-UCS2");return le.create({encoding:d,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(a){var r=e.cMap;t=[];r.forEach(function(e,r){R(r<=65535,"Max size of CID is 65,535");var i=a.lookup(r);i&&(t[e]=String.fromCharCode((i.charCodeAt(0)<<8)+i.charCodeAt(1)))});return new re(t)})}return Promise.resolve(new ae(e.firstChar,e.lastChar))},readToUnicode:function(e){var t=e;return z(t)?le.create({encoding:t,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){return e instanceof he?new ae(0,65535):new re(e.getMap())}):G(t)?le.create({encoding:t,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){if(e instanceof he)return new ae(0,65535);var t=new Array(e.length);e.forEach(function(e,a){for(var r=[],i=0;i<a.length;i+=2){var n=a.charCodeAt(i)<<8|a.charCodeAt(i+1);if(55296==(63488&n)){i+=2;var s=a.charCodeAt(i)<<8|a.charCodeAt(i+1);r.push(((1023&n)<<10)+(1023&s)+65536)}else r.push(n)}t[e]=String.fromCharCode.apply(String,r)});return new re(t)}):Promise.resolve(null)},readCidToGidMap:function(e){for(var t=e.getBytes(),a=[],r=0,i=t.length;r<i;r++){var n=t[r++]<<8|t[r];if(0!==n){a[r>>1]=n}}return a},extractWidths:function(e,t,a){var r,i,n,s,o,c,l,h,u=this.xref,f=[],d=0,g=[];if(a.composite){d=e.get("DW")||1e3;h=e.get("W");if(h)for(i=0,n=h.length;i<n;i++){c=u.fetchIfRef(h[i++]);l=u.fetchIfRef(h[i]);if(M(l))for(s=0,o=l.length;s<o;s++)f[c++]=u.fetchIfRef(l[s]);else{var p=u.fetchIfRef(h[++i]);for(s=c;s<=l;s++)f[s]=p}}if(a.vertical){var m=e.getArray("DW2")||[880,-1e3];r=[m[1],.5*d,m[0]];m=e.get("W2");if(m)for(i=0,n=m.length;i<n;i++){c=u.fetchIfRef(m[i++]);l=u.fetchIfRef(m[i]);if(M(l))for(s=0,o=l.length;s<o;s++)g[c++]=[u.fetchIfRef(l[s++]),u.fetchIfRef(l[s++]),u.fetchIfRef(l[s])];else{var b=[u.fetchIfRef(m[++i]),u.fetchIfRef(m[++i]),u.fetchIfRef(m[++i])];for(s=c;s<=l;s++)g[s]=b}}}}else{var v=a.firstChar;h=e.get("Widths");if(h){s=v;for(i=0,n=h.length;i<n;i++)f[s++]=u.fetchIfRef(h[i]);d=parseFloat(t.get("MissingWidth"))||0}else{var y=e.get("BaseFont");if(z(y)){var k=this.getBaseFontMetrics(y.name);f=this.buildCharCodeToWidth(k.widths,a);d=k.defaultWidth}}}var w=!0,C=d;for(var x in f){var S=f[x];if(S)if(C){if(C!==S){w=!1;break}}else C=S}w&&(a.flags|=ee.FixedPitch);a.defaultWidth=d;a.widths=f;a.defaultVMetrics=r;a.vmetrics=g},isSerifFont:function(e){var t=e.split("-")[0];return t in ke()||-1!==t.search(/serif/gi)},getBaseFontMetrics:function(e){var t=0,a=[],r=!1,i=ye(),n=i[e]||e,s=ue();n in s||(n=this.isSerifFont(e)?"Times-Roman":"Helvetica");var o=s[n];if(E(o)){t=o;r=!0}else a=o();return{defaultWidth:t,monospace:r,widths:a}},buildCharCodeToWidth:function(e,t){for(var a=Object.create(null),r=t.differences,i=t.defaultEncoding,n=0;n<256;n++)n in r&&e[r[n]]?a[n]=e[r[n]]:n in i&&e[i[n]]&&(a[n]=e[i[n]]);return a},preEvaluateFont:function(e){var t=e,a=e.get("Subtype");R(z(a),"invalid font Subtype");var r,i=!1;if("Type0"===a.name){var n=e.get("DescendantFonts");R(n,"Descendant fonts are not specified");e=M(n)?this.xref.fetchIfRef(n[0]):n;a=e.get("Subtype");R(z(a),"invalid font Subtype");i=!0}var s=e.get("FontDescriptor");if(s){var o=new Q,c=t.getRaw("Encoding");if(z(c))o.update(c.name);else if(H(c))o.update(c.toString());else if(_(c))for(var l=c.getKeys(),h=0,u=l.length;h<u;h++){var f=c.getRaw(l[h]);if(z(f))o.update(f.name);else if(H(f))o.update(f.toString());else if(M(f)){for(var d=f.length,g=new Array(d),p=0;p<d;p++){var m=f[p];z(m)?g[p]=m.name:(E(m)||H(m))&&(g[p]=m.toString())}o.update(g.join())}}var b=e.get("ToUnicode")||t.get("ToUnicode");if(G(b)){var v=b.str||b;r=v.buffer?new Uint8Array(v.buffer.buffer,0,v.bufferLength):new Uint8Array(v.bytes.buffer,v.start,v.end-v.start);o.update(r)}else z(b)&&o.update(b.name);var y=e.get("Widths")||t.get("Widths");if(y){r=new Uint8Array(new Uint32Array(y).buffer);o.update(r)}}return{descriptor:s,dict:e,baseDict:t,composite:i,type:a.name,hash:o?o.hexdigest():""}},translateFont:function(e){var t,a=e.baseDict,r=e.dict,i=e.composite,n=e.descriptor,s=e.type,o=i?65535:255;if(!n){if("Type3"!==s){var c=r.get("BaseFont");R(z(c),"Base font is not specified");c=c.name.replace(/[,_]/g,"-");var l=this.getBaseFontMetrics(c),h=c.split("-")[0],u=(this.isSerifFont(h)?ee.Serif:0)|(l.monospace?ee.FixedPitch:0)|(we()[h]?ee.Symbolic:ee.Nonsymbolic);t={type:s,name:c,widths:l.widths,defaultWidth:l.defaultWidth,flags:u,firstChar:0,lastChar:o};return this.extractDataStructures(r,r,t).then(function(e){e.widths=this.buildCharCodeToWidth(l.widths,e);return new te(c,null,e)}.bind(this))}n=new q(null);n.set("FontName",U.get(s));n.set("FontBBox",r.getArray("FontBBox"))}var f=r.get("FirstChar")||0,d=r.get("LastChar")||o,g=n.get("FontName"),p=r.get("BaseFont");L(g)&&(g=U.get(g));L(p)&&(p=U.get(p));if("Type3"!==s){var m=g&&g.name,b=p&&p.name;if(m!==b){P("The FontDescriptor's FontName is \""+m+'" but should be the same as the Font\'s BaseFont "'+b+'"');m&&b&&0===b.indexOf(m)&&(g=p)}}g=g||p;R(z(g),"invalid font name");var v=n.get("FontFile","FontFile2","FontFile3");if(v&&v.dict){var y=v.dict.get("Subtype");y&&(y=y.name);var w=v.dict.get("Length1"),C=v.dict.get("Length2"),x=v.dict.get("Length3")}t={type:s,name:g.name,subtype:y,file:v,length1:w,length2:C,length3:x,loadedName:a.loadedName,composite:i,wideChars:i,fixedPitch:!1,fontMatrix:r.getArray("FontMatrix")||k,firstChar:f||0,lastChar:d||o,bbox:n.getArray("FontBBox"),ascent:n.get("Ascent"),descent:n.get("Descent"),xHeight:n.get("XHeight"),capHeight:n.get("CapHeight"),flags:n.get("Flags"),italicAngle:n.get("ItalicAngle"),coded:!1};var S;if(i){var A=a.get("Encoding");z(A)&&(t.cidEncoding=A.name);S=le.create({encoding:A,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){t.cMap=e;t.vertical=t.cMap.vertical})}else S=Promise.resolve(void 0);return S.then(function(){return this.extractDataStructures(r,a,t)}.bind(this)).then(function(e){this.extractWidths(r,n,e);"Type3"===s&&(e.isType3Font=!0);return new te(g.name,v,e)}.bind(this))}};return t}(),Be=function(){function e(e,t,a){this.loadedName=e;this.font=t;this.dict=a;this.type3Loaded=null;this.sent=!1}e.prototype={send:function(e){if(!this.sent){var t=this.font.exportData();e.send("commonobj",[this.loadedName,"Font",t]);this.sent=!0}},loadType3Data:function(e,t,a,r){R(this.font.isType3Font);if(this.type3Loaded)return this.type3Loaded;for(var i=this.font,n=Promise.resolve(),s=this.dict.get("CharProcs"),o=this.dict.get("Resources")||t,c=s.getKeys(),l=Object.create(null),h=0,u=c.length;h<u;++h)n=n.then(function(t){var i=s.get(t),n=new Re;return e.getOperatorList(i,r,o,n).then(function(){l[t]=n.getIR();a.addDependencies(n.dependencies)},function(e){F('Type3 font resource "'+t+'" is not available');var a=new Re;l[t]=a.getIR()})}.bind(this,c[h]));this.type3Loaded=n.then(function(){i.charProcOperatorList=l});return this.type3Loaded}};return e}(),Re=function(){function e(e){for(var t=[],a=e.fnArray,r=e.argsArray,i=0,n=e.length;i<n;i++)switch(a[i]){case S.paintInlineImageXObject:case S.paintInlineImageXObjectGroup:case S.paintImageMaskXObject:var s=r[i][0];s.cached||t.push(s.data.buffer)}return t}function t(e,t,a){this.messageHandler=t;this.fnArray=[];this.argsArray=[];this.dependencies=Object.create(null);this._totalLength=0;this.pageIndex=a;this.intent=e}t.prototype={get length(){return this.argsArray.length},get totalLength(){return this._totalLength+this.length},addOp:function(e,t){this.fnArray.push(e);this.argsArray.push(t);this.messageHandler&&(this.fnArray.length>=1e3?this.flush():this.fnArray.length>=995&&(e===S.restore||e===S.endText)&&this.flush())},addDependency:function(e){if(!(e in this.dependencies)){this.dependencies[e]=!0;this.addOp(S.dependency,[e])}},addDependencies:function(e){for(var t in e)this.addDependency(t)},addOpList:function(e){B.extendObj(this.dependencies,e.dependencies);for(var t=0,a=e.length;t<a;t++)this.addOp(e.fnArray[t],e.argsArray[t])},getIR:function(){return{fnArray:this.fnArray,argsArray:this.argsArray,length:this.length}},flush:function(t){"oplist"!==this.intent&&(new Ee).optimize(this);var a=e(this),r=this.length;this._totalLength+=r;this.messageHandler.send("RenderPageChunk",{operatorList:{fnArray:this.fnArray,argsArray:this.argsArray,lastChunk:t,length:r},pageIndex:this.pageIndex,intent:this.intent},a);this.dependencies=Object.create(null);this.fnArray.length=0;this.argsArray.length=0}};return t}(),Te=function(){function e(e){this.state=e;this.stateStack=[]}e.prototype={save:function(){var e=this.state;this.stateStack.push(this.state);this.state=e.clone()},restore:function(){var e=this.stateStack.pop();e&&(this.state=e)},transform:function(e){this.state.ctm=B.transform(this.state.ctm,e)}};return e}(),Oe=function(){function e(){this.ctm=new Float32Array(w);this.fontName=null;this.fontSize=0;this.font=null;this.fontMatrix=k;this.textMatrix=w.slice();this.textLineMatrix=w.slice();this.charSpacing=0;this.wordSpacing=0;this.leading=0;this.textHScale=1;this.textRise=0}e.prototype={setTextMatrix:function(e,t,a,r,i,n){var s=this.textMatrix;s[0]=e;s[1]=t;s[2]=a;s[3]=r;s[4]=i;s[5]=n},setTextLineMatrix:function(e,t,a,r,i,n){var s=this.textLineMatrix;s[0]=e;s[1]=t;s[2]=a;s[3]=r;s[4]=i;s[5]=n},translateTextMatrix:function(e,t){var a=this.textMatrix;a[4]=a[0]*e+a[2]*t+a[4];a[5]=a[1]*e+a[3]*t+a[5]},translateTextLineMatrix:function(e,t){var a=this.textLineMatrix;a[4]=a[0]*e+a[2]*t+a[4];a[5]=a[1]*e+a[3]*t+a[5]},calcTextLineMatrixAdvance:function(e,t,a,r,i,n){var s=this.font;if(!s)return null;var o=this.textLineMatrix;if(e!==o[0]||t!==o[1]||a!==o[2]||r!==o[3])return null;var c=i-o[4],l=n-o[5];if(s.vertical&&0!==c||!s.vertical&&0!==l)return null;var h,u,f=e*r-t*a;if(s.vertical){h=-l*a/f;u=l*e/f}else{h=c*r/f;u=-c*t/f}return{width:h,height:u,value:s.vertical?u:h}},calcRenderMatrix:function(e){var t=[this.fontSize*this.textHScale,0,0,this.fontSize,0,this.textRise];return B.transform(e,B.transform(this.textMatrix,t))},carriageReturn:function(){this.translateTextLineMatrix(0,-this.leading);this.textMatrix=this.textLineMatrix.slice()},clone:function(){var e=Object.create(this);e.textMatrix=this.textMatrix.slice();e.textLineMatrix=this.textLineMatrix.slice();e.fontMatrix=this.fontMatrix.slice();return e}};return e}(),Pe=function(){function e(){this.ctm=new Float32Array(w);this.font=null;this.textRenderingMode=A.FILL;this.fillColorSpace=Z.singletons.gray;this.strokeColorSpace=Z.singletons.gray}e.prototype={clone:function(){return Object.create(this)}};return e}(),Me=function(){function e(e,a,r){this.opMap=t();this.parser=new Y(new K(e,this.opMap),!1,a);this.stateManager=r;this.nonProcessedArgs=[]}var t=D(function(e){e.w={id:S.setLineWidth,numArgs:1,variableArgs:!1};e.J={id:S.setLineCap,numArgs:1,variableArgs:!1};e.j={id:S.setLineJoin,numArgs:1,variableArgs:!1};e.M={id:S.setMiterLimit,numArgs:1,variableArgs:!1};e.d={id:S.setDash,numArgs:2,variableArgs:!1};e.ri={id:S.setRenderingIntent,numArgs:1,variableArgs:!1};e.i={id:S.setFlatness,numArgs:1,variableArgs:!1};e.gs={id:S.setGState,numArgs:1,variableArgs:!1};e.q={id:S.save,numArgs:0,variableArgs:!1};e.Q={id:S.restore,numArgs:0,variableArgs:!1};e.cm={id:S.transform,numArgs:6,variableArgs:!1};e.m={id:S.moveTo,numArgs:2,variableArgs:!1};e.l={id:S.lineTo,numArgs:2,variableArgs:!1};e.c={id:S.curveTo,numArgs:6,variableArgs:!1};e.v={id:S.curveTo2,numArgs:4,variableArgs:!1};e.y={id:S.curveTo3,numArgs:4,variableArgs:!1};e.h={id:S.closePath,numArgs:0,variableArgs:!1};e.re={id:S.rectangle,numArgs:4,variableArgs:!1};e.S={id:S.stroke,numArgs:0,variableArgs:!1};e.s={id:S.closeStroke,numArgs:0,variableArgs:!1};e.f={id:S.fill,numArgs:0,variableArgs:!1};e.F={id:S.fill,numArgs:0,variableArgs:!1};e["f*"]={id:S.eoFill,numArgs:0,variableArgs:!1};e.B={id:S.fillStroke,numArgs:0,variableArgs:!1};e["B*"]={id:S.eoFillStroke,numArgs:0,variableArgs:!1};e.b={id:S.closeFillStroke,numArgs:0,variableArgs:!1};e["b*"]={id:S.closeEOFillStroke,numArgs:0,variableArgs:!1};e.n={id:S.endPath,numArgs:0,variableArgs:!1};e.W={id:S.clip,numArgs:0,variableArgs:!1};e["W*"]={id:S.eoClip,numArgs:0,variableArgs:!1};e.BT={id:S.beginText,numArgs:0,variableArgs:!1};e.ET={id:S.endText,numArgs:0,variableArgs:!1};e.Tc={id:S.setCharSpacing,numArgs:1,variableArgs:!1};e.Tw={id:S.setWordSpacing,numArgs:1,variableArgs:!1};e.Tz={id:S.setHScale,numArgs:1,variableArgs:!1};e.TL={id:S.setLeading,numArgs:1,variableArgs:!1};e.Tf={id:S.setFont,numArgs:2,variableArgs:!1};e.Tr={id:S.setTextRenderingMode,numArgs:1,variableArgs:!1};e.Ts={id:S.setTextRise,numArgs:1,variableArgs:!1};e.Td={id:S.moveText,numArgs:2,variableArgs:!1};e.TD={id:S.setLeadingMoveText,numArgs:2,variableArgs:!1};e.Tm={id:S.setTextMatrix,numArgs:6,variableArgs:!1};e["T*"]={id:S.nextLine,numArgs:0,variableArgs:!1};e.Tj={id:S.showText,numArgs:1,variableArgs:!1};e.TJ={id:S.showSpacedText,numArgs:1,variableArgs:!1};e["'"]={id:S.nextLineShowText,numArgs:1,variableArgs:!1};e['"']={id:S.nextLineSetSpacingShowText,numArgs:3,variableArgs:!1};e.d0={id:S.setCharWidth,numArgs:2,variableArgs:!1};e.d1={id:S.setCharWidthAndBounds,numArgs:6,variableArgs:!1};e.CS={id:S.setStrokeColorSpace,numArgs:1,variableArgs:!1};e.cs={id:S.setFillColorSpace,numArgs:1,variableArgs:!1};e.SC={id:S.setStrokeColor,numArgs:4,variableArgs:!0};e.SCN={id:S.setStrokeColorN,numArgs:33,variableArgs:!0};e.sc={id:S.setFillColor,numArgs:4,variableArgs:!0};e.scn={id:S.setFillColorN,numArgs:33,variableArgs:!0};e.G={id:S.setStrokeGray,numArgs:1,variableArgs:!1};e.g={id:S.setFillGray,numArgs:1,variableArgs:!1};e.RG={id:S.setStrokeRGBColor,numArgs:3,variableArgs:!1};e.rg={id:S.setFillRGBColor,numArgs:3,variableArgs:!1};e.K={id:S.setStrokeCMYKColor,numArgs:4,variableArgs:!1};e.k={id:S.setFillCMYKColor,numArgs:4,variableArgs:!1};e.sh={id:S.shadingFill,numArgs:1,variableArgs:!1};e.BI={id:S.beginInlineImage,numArgs:0,variableArgs:!1};e.ID={id:S.beginImageData,numArgs:0,variableArgs:!1};e.EI={id:S.endInlineImage,numArgs:1,variableArgs:!1};e.Do={id:S.paintXObject,numArgs:1,variableArgs:!1};e.MP={id:S.markPoint,numArgs:1,variableArgs:!1};e.DP={id:S.markPointProps,numArgs:2,variableArgs:!1};e.BMC={id:S.beginMarkedContent,numArgs:1,variableArgs:!1};e.BDC={id:S.beginMarkedContentProps,numArgs:2,variableArgs:!1};e.EMC={id:S.endMarkedContent,numArgs:0,variableArgs:!1};e.BX={id:S.beginCompat,numArgs:0,variableArgs:!1};e.EX={id:S.endCompat,numArgs:0,variableArgs:!1};e.BM=null;e.BD=null;e.true=null;e.fa=null;e.fal=null;e.fals=null;e.false=null;e.nu=null;e.nul=null;e.null=null});e.prototype={get savedStatesDepth(){return this.stateManager.stateStack.length},read:function(e){for(var t=e.args;;){var a=this.parser.getObj();if(j(a)){var r=a.cmd,i=this.opMap[r];if(!i){F('Unknown command "'+r+'"');continue}var n=i.id,s=i.numArgs,o=null!==t?t.length:0;if(i.variableArgs)o>s&&P("Command "+n+": expected [0,"+s+"] args, but received "+o+" args.");else{if(o!==s){for(var c=this.nonProcessedArgs;o>s;){c.push(t.shift());o--}for(;o<s&&0!==c.length;){null===t&&(t=[]);t.unshift(c.pop());o++}}if(o<s){F("Skipping command "+n+": expected "+s+" args, but received "+o+" args.");null!==t&&(t.length=0);continue}}this.preprocessCommand(n,t);e.fn=n;e.args=t;return!0}if(N(a))return!1;if(null!==a){null===t&&(t=[]);t.push(a);R(t.length<=33,"Too many arguments")}}},preprocessCommand:function(e,t){switch(0|e){case S.save:this.stateManager.save();break;case S.restore:this.stateManager.restore();break;case S.transform:this.stateManager.transform(t)}}};return e}(),Ee=function(){function e(e,t,a){for(var r=e,i=0,n=t.length-1;i<n;i++){var s=t[i];r=r[s]||(r[s]=[])}r[t[t.length-1]]=a}function t(e,t,a,r){for(var i=e+2,n=0;n<t;n++){var s=r[i+4*n],o=1===s.length&&s[0];if(!o||1!==o.width||1!==o.height||o.data.length&&(1!==o.data.length||0!==o.data[0]))break;a[i+4*n]=S.paintSolidColorImageMask}return t-n}function a(){}var r=[];e(r,[S.save,S.transform,S.paintInlineImageXObject,S.restore],function(e){for(var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-3,n=r-2,s=r-1,o=i+4,c=t.length;o+3<c&&t[o]===S.save&&t[o+1]===S.transform&&t[o+2]===S.paintInlineImageXObject&&t[o+3]===S.restore;)o+=4;var l=Math.min((o-i)/4,200);if(l<10)return o;var h,u=0,f=[],d=0,g=1,p=1;for(h=0;h<l;h++){var m=a[n+(h<<2)],b=a[s+(h<<2)][0];if(g+b.width>1e3){u=Math.max(u,g);p+=d+2;g=0;d=0}f.push({transform:m,x:g,y:p,w:b.width,h:b.height});g+=b.width+2;d=Math.max(d,b.height)}var v=Math.max(u,g)+1,y=p+d+1,k=new Uint8Array(v*y*4),w=v<<2;for(h=0;h<l;h++){var C=a[s+(h<<2)][0].data,A=f[h].w<<2,I=0,B=f[h].x+f[h].y*v<<2;k.set(C.subarray(0,A),B-w);for(var R=0,T=f[h].h;R<T;R++){k.set(C.subarray(I,I+A),B);I+=A;B+=w}k.set(C.subarray(I-A,I),B);for(;B>=0;){C[B-4]=C[B];C[B-3]=C[B+1];C[B-2]=C[B+2];C[B-1]=C[B+3];C[B+A]=C[B+A-4];C[B+A+1]=C[B+A-3];C[B+A+2]=C[B+A-2];C[B+A+3]=C[B+A-1];B-=w}}t.splice(i,4*l,S.paintInlineImageXObjectGroup);a.splice(i,4*l,[{width:v,height:y,kind:x.RGBA_32BPP,data:k},f]);return i+1});e(r,[S.save,S.transform,S.paintImageMaskXObject,S.restore],function(e){for(var a=e.fnArray,r=e.argsArray,i=e.iCurr,n=i-3,s=i-2,o=i-1,c=n+4,l=a.length;c+3<l&&a[c]===S.save&&a[c+1]===S.transform&&a[c+2]===S.paintImageMaskXObject&&a[c+3]===S.restore;)c+=4;var h=(c-n)/4;h=t(n,h,a,r);if(h<10)return c;var u,f,d,g=!1,p=r[o][0];if(0===r[s][1]&&0===r[s][2]){g=!0;var m=r[s][0],b=r[s][3];f=s+4;var v=o+4;for(u=1;u<h;u++,f+=4,v+=4){d=r[f];if(r[v][0]!==p||d[0]!==m||0!==d[1]||0!==d[2]||d[3]!==b){u<10?g=!1:h=u;break}}}if(g){h=Math.min(h,1e3);var y=new Float32Array(2*h);f=s;for(u=0;u<h;u++,f+=4){d=r[f];y[u<<1]=d[4];y[1+(u<<1)]=d[5]}a.splice(n,4*h,S.paintImageMaskXObjectRepeat);r.splice(n,4*h,[p,m,b,y])}else{h=Math.min(h,100);var k=[];for(u=0;u<h;u++){d=r[s+(u<<2)];var w=r[o+(u<<2)][0];k.push({data:w.data,width:w.width,height:w.height,transform:d})}a.splice(n,4*h,S.paintImageMaskXObjectGroup);r.splice(n,4*h,[k])}return n+1});e(r,[S.save,S.transform,S.paintImageXObject,S.restore],function(e){var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-3,n=r-2,s=r-1,o=r;if(0!==a[n][1]||0!==a[n][2])return o+1;for(var c=a[s][0],l=a[n][0],h=a[n][3],u=i+4,f=t.length;u+3<f&&t[u]===S.save&&t[u+1]===S.transform&&t[u+2]===S.paintImageXObject&&t[u+3]===S.restore&&a[u+1][0]===l&&0===a[u+1][1]&&0===a[u+1][2]&&a[u+1][3]===h&&a[u+2][0]===c;)u+=4;var d=Math.min((u-i)/4,1e3);if(d<3)return u;for(var g=new Float32Array(2*d),p=n,m=0;m<d;m++,p+=4){var b=a[p];g[m<<1]=b[4];g[1+(m<<1)]=b[5]}var v=[c,l,h,g];t.splice(i,4*d,S.paintImageXObjectRepeat);a.splice(i,4*d,v);return i+1});e(r,[S.beginText,S.setFont,S.setTextMatrix,S.showText,S.endText],function(e){for(var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-4,n=r-3,s=r-2,o=r-1,c=r,l=a[n][0],h=a[n][1],u=i+5,f=t.length;u+4<f&&t[u]===S.beginText&&t[u+1]===S.setFont&&t[u+2]===S.setTextMatrix&&t[u+3]===S.showText&&t[u+4]===S.endText&&a[u+1][0]===l&&a[u+1][1]===h;)u+=5;var d=Math.min((u-i)/5,1e3);if(d<3)return u;var g=i;if(i>=4&&t[i-4]===t[n]&&t[i-3]===t[s]&&t[i-2]===t[o]&&t[i-1]===t[c]&&a[i-4][0]===l&&a[i-4][1]===h){d++;g-=5}for(var p=g+4,m=1;m<d;m++){t.splice(p,3);a.splice(p,3);p+=2}return p+1});a.prototype={optimize:function(e){for(var t,a=e.fnArray,i=e.argsArray,n={iCurr:0,fnArray:a,argsArray:i},s=0,o=a.length;s<o;){t=(t||r)[a[s]];if("function"==typeof t){n.iCurr=s;s=t(n);t=void 0;o=n.fnArray.length}else s++}}};return a}();t.OperatorList=Re;t.PartialEvaluator=Ie},function(e,t,a){"use strict";var r=a(0),i=a(10),n=r.info,s=r.warn,o=r.error,c=r.log2,l=r.readUint16,h=r.readUint32,u=i.ArithmeticDecoder,f=function(){function e(){this.failOnCorruptedImage=!1}function t(e,t){e.x0=Math.ceil(t.XOsiz/e.XRsiz);e.x1=Math.ceil(t.Xsiz/e.XRsiz);e.y0=Math.ceil(t.YOsiz/e.YRsiz);e.y1=Math.ceil(t.Ysiz/e.YRsiz);e.width=e.x1-e.x0;e.height=e.y1-e.y0}function a(e,t){for(var a,r=e.SIZ,i=[],n=Math.ceil((r.Xsiz-r.XTOsiz)/r.XTsiz),s=Math.ceil((r.Ysiz-r.YTOsiz)/r.YTsiz),o=0;o<s;o++)for(var c=0;c<n;c++){a={};a.tx0=Math.max(r.XTOsiz+c*r.XTsiz,r.XOsiz);a.ty0=Math.max(r.YTOsiz+o*r.YTsiz,r.YOsiz);a.tx1=Math.min(r.XTOsiz+(c+1)*r.XTsiz,r.Xsiz);a.ty1=Math.min(r.YTOsiz+(o+1)*r.YTsiz,r.Ysiz);a.width=a.tx1-a.tx0;a.height=a.ty1-a.ty0;a.components=[];i.push(a)}e.tiles=i;for(var l=r.Csiz,h=0,u=l;h<u;h++)for(var f=t[h],d=0,g=i.length;d<g;d++){var p={};a=i[d];p.tcx0=Math.ceil(a.tx0/f.XRsiz);p.tcy0=Math.ceil(a.ty0/f.YRsiz);p.tcx1=Math.ceil(a.tx1/f.XRsiz);p.tcy1=Math.ceil(a.ty1/f.YRsiz);p.width=p.tcx1-p.tcx0;p.height=p.tcy1-p.tcy0;a.components[h]=p}}function r(e,t,a){var r=t.codingStyleParameters,i={};if(r.entropyCoderWithCustomPrecincts){i.PPx=r.precinctsSizes[a].PPx;i.PPy=r.precinctsSizes[a].PPy}else{i.PPx=15;i.PPy=15}i.xcb_=a>0?Math.min(r.xcb,i.PPx-1):Math.min(r.xcb,i.PPx);i.ycb_=a>0?Math.min(r.ycb,i.PPy-1):Math.min(r.ycb,i.PPy);return i}function i(e,t,a){var r=1<<a.PPx,i=1<<a.PPy,n=0===t.resLevel,s=1<<a.PPx+(n?0:-1),o=1<<a.PPy+(n?0:-1),c=t.trx1>t.trx0?Math.ceil(t.trx1/r)-Math.floor(t.trx0/r):0,l=t.try1>t.try0?Math.ceil(t.try1/i)-Math.floor(t.try0/i):0,h=c*l;t.precinctParameters={ +precinctWidth:r,precinctHeight:i,numprecinctswide:c,numprecinctshigh:l,numprecincts:h,precinctWidthInSubband:s,precinctHeightInSubband:o}}function f(e,t,a){var r,i,n,s,o=a.xcb_,c=a.ycb_,l=1<<o,h=1<<c,u=t.tbx0>>o,f=t.tby0>>c,d=t.tbx1+l-1>>o,g=t.tby1+h-1>>c,p=t.resolution.precinctParameters,m=[],b=[];for(i=f;i<g;i++)for(r=u;r<d;r++){n={cbx:r,cby:i,tbx0:l*r,tby0:h*i,tbx1:l*(r+1),tby1:h*(i+1)};n.tbx0_=Math.max(t.tbx0,n.tbx0);n.tby0_=Math.max(t.tby0,n.tby0);n.tbx1_=Math.min(t.tbx1,n.tbx1);n.tby1_=Math.min(t.tby1,n.tby1);var v=Math.floor((n.tbx0_-t.tbx0)/p.precinctWidthInSubband),y=Math.floor((n.tby0_-t.tby0)/p.precinctHeightInSubband);s=v+y*p.numprecinctswide;n.precinctNumber=s;n.subbandType=t.type;n.Lblock=3;if(!(n.tbx1_<=n.tbx0_||n.tby1_<=n.tby0_)){m.push(n);var k=b[s];if(void 0!==k){r<k.cbxMin?k.cbxMin=r:r>k.cbxMax&&(k.cbxMax=r);i<k.cbyMin?k.cbxMin=i:i>k.cbyMax&&(k.cbyMax=i)}else b[s]=k={cbxMin:r,cbyMin:i,cbxMax:r,cbyMax:i};n.precinct=k}}t.codeblockParameters={codeblockWidth:o,codeblockHeight:c,numcodeblockwide:d-u+1,numcodeblockhigh:g-f+1};t.codeblocks=m;t.precincts=b}function d(e,t,a){for(var r=[],i=e.subbands,n=0,s=i.length;n<s;n++)for(var o=i[n],c=o.codeblocks,l=0,h=c.length;l<h;l++){var u=c[l];u.precinctNumber===t&&r.push(u)}return{layerNumber:a,codeblocks:r}}function g(e){for(var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=0,c=0;c<n;c++)s=Math.max(s,r.components[c].codingStyleParameters.decompositionLevelsCount);var l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;l<i;l++){for(;h<=s;h++){for(;u<n;u++){var e=r.components[u];if(!(h>e.codingStyleParameters.decompositionLevelsCount)){for(var t=e.resolutions[h],a=t.precinctParameters.numprecincts;f<a;){var c=d(t,f,l);f++;return c}f=0}}u=0}h=0}o("JPX Error: Out of packets")}}function p(e){for(var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=0,c=0;c<n;c++)s=Math.max(s,r.components[c].codingStyleParameters.decompositionLevelsCount);var l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;l<=s;l++){for(;h<i;h++){for(;u<n;u++){var e=r.components[u];if(!(l>e.codingStyleParameters.decompositionLevelsCount)){for(var t=e.resolutions[l],a=t.precinctParameters.numprecincts;f<a;){var c=d(t,f,h);f++;return c}f=0}}u=0}h=0}o("JPX Error: Out of packets")}}function m(e){var t,a,r,i,n=e.SIZ,s=e.currentTile.index,c=e.tiles[s],l=c.codingStyleDefaultParameters.layersCount,h=n.Csiz,u=0;for(r=0;r<h;r++){var f=c.components[r];u=Math.max(u,f.codingStyleParameters.decompositionLevelsCount)}var g=new Int32Array(u+1);for(a=0;a<=u;++a){var p=0;for(r=0;r<h;++r){var m=c.components[r].resolutions;a<m.length&&(p=Math.max(p,m[a].precinctParameters.numprecincts))}g[a]=p}t=0;a=0;r=0;i=0;this.nextPacket=function(){for(;a<=u;a++){for(;i<g[a];i++){for(;r<h;r++){var e=c.components[r];if(!(a>e.codingStyleParameters.decompositionLevelsCount)){var n=e.resolutions[a],s=n.precinctParameters.numprecincts;if(!(i>=s)){for(;t<l;){var f=d(n,i,t);t++;return f}t=0}}}r=0}i=0}o("JPX Error: Out of packets")}}function b(e){var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=k(r),c=s,l=0,h=0,u=0,f=0,g=0;this.nextPacket=function(){for(;g<c.maxNumHigh;g++){for(;f<c.maxNumWide;f++){for(;u<n;u++){for(var e=r.components[u],t=e.codingStyleParameters.decompositionLevelsCount;h<=t;h++){var a=e.resolutions[h],p=s.components[u].resolutions[h],m=y(f,g,p,c,a);if(null!==m){for(;l<i;){var b=d(a,m,l);l++;return b}l=0}}h=0}u=0}f=0}o("JPX Error: Out of packets")}}function v(e){var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=k(r),c=0,l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;h<n;++h){for(var e=r.components[h],t=s.components[h],a=e.codingStyleParameters.decompositionLevelsCount;f<t.maxNumHigh;f++){for(;u<t.maxNumWide;u++){for(;l<=a;l++){var g=e.resolutions[l],p=t.resolutions[l],m=y(u,f,p,t,g);if(null!==m){for(;c<i;){var b=d(g,m,c);c++;return b}c=0}}l=0}u=0}f=0}o("JPX Error: Out of packets")}}function y(e,t,a,r,i){var n=e*r.minWidth,s=t*r.minHeight;if(n%a.width!=0||s%a.height!=0)return null;var o=s/a.width*i.precinctParameters.numprecinctswide;return n/a.height+o}function k(e){for(var t=e.components.length,a=Number.MAX_VALUE,r=Number.MAX_VALUE,i=0,n=0,s=new Array(t),o=0;o<t;o++){for(var c=e.components[o],l=c.codingStyleParameters.decompositionLevelsCount,h=new Array(l+1),u=Number.MAX_VALUE,f=Number.MAX_VALUE,d=0,g=0,p=1,m=l;m>=0;--m){var b=c.resolutions[m],v=p*b.precinctParameters.precinctWidth,y=p*b.precinctParameters.precinctHeight;u=Math.min(u,v);f=Math.min(f,y);d=Math.max(d,b.precinctParameters.numprecinctswide);g=Math.max(g,b.precinctParameters.numprecinctshigh);h[m]={width:v,height:y};p<<=1}a=Math.min(a,u);r=Math.min(r,f);i=Math.max(i,d);n=Math.max(n,g);s[o]={resolutions:h,minWidth:u,minHeight:f,maxNumWide:d,maxNumHigh:g}}return{components:s,minWidth:a,minHeight:r,maxNumWide:i,maxNumHigh:n}}function w(e){for(var t=e.SIZ,a=e.currentTile.index,n=e.tiles[a],s=t.Csiz,c=0;c<s;c++){for(var l=n.components[c],h=l.codingStyleParameters.decompositionLevelsCount,u=[],d=[],y=0;y<=h;y++){var k=r(e,l,y),w={},C=1<<h-y;w.trx0=Math.ceil(l.tcx0/C);w.try0=Math.ceil(l.tcy0/C);w.trx1=Math.ceil(l.tcx1/C);w.try1=Math.ceil(l.tcy1/C);w.resLevel=y;i(e,w,k);u.push(w);var x;if(0===y){x={};x.type="LL";x.tbx0=Math.ceil(l.tcx0/C);x.tby0=Math.ceil(l.tcy0/C);x.tbx1=Math.ceil(l.tcx1/C);x.tby1=Math.ceil(l.tcy1/C);x.resolution=w;f(e,x,k);d.push(x);w.subbands=[x]}else{var S=1<<h-y+1,A=[];x={};x.type="HL";x.tbx0=Math.ceil(l.tcx0/S-.5);x.tby0=Math.ceil(l.tcy0/S);x.tbx1=Math.ceil(l.tcx1/S-.5);x.tby1=Math.ceil(l.tcy1/S);x.resolution=w;f(e,x,k);d.push(x);A.push(x);x={};x.type="LH";x.tbx0=Math.ceil(l.tcx0/S);x.tby0=Math.ceil(l.tcy0/S-.5);x.tbx1=Math.ceil(l.tcx1/S);x.tby1=Math.ceil(l.tcy1/S-.5);x.resolution=w;f(e,x,k);d.push(x);A.push(x);x={};x.type="HH";x.tbx0=Math.ceil(l.tcx0/S-.5);x.tby0=Math.ceil(l.tcy0/S-.5);x.tbx1=Math.ceil(l.tcx1/S-.5);x.tby1=Math.ceil(l.tcy1/S-.5);x.resolution=w;f(e,x,k);d.push(x);A.push(x);w.subbands=A}}l.resolutions=u;l.subbands=d}var I=n.codingStyleDefaultParameters.progressionOrder;switch(I){case 0:n.packetsIterator=new g(e);break;case 1:n.packetsIterator=new p(e);break;case 2:n.packetsIterator=new m(e);break;case 3:n.packetsIterator=new b(e);break;case 4:n.packetsIterator=new v(e);break;default:o("JPX Error: Unsupported progression order "+I)}}function C(e,t,a,r){function i(e){for(;u<e;){var r=t[a+h];h++;if(f){l=l<<7|r;u+=7;f=!1}else{l=l<<8|r;u+=8}255===r&&(f=!0)}u-=e;return l>>>u&(1<<e)-1}function n(e){if(255===t[a+h-1]&&t[a+h]===e){s(1);return!0}if(255===t[a+h]&&t[a+h+1]===e){s(2);return!0}return!1}function s(e){h+=e}function o(){u=0;if(f){h++;f=!1}}for(var l,h=0,u=0,f=!1,d=e.currentTile.index,g=e.tiles[d],p=e.COD.sopMarkerUsed,m=e.COD.ephMarkerUsed,b=g.packetsIterator;h<r;){o();p&&n(145)&&s(4);var v=b.nextPacket();if(i(1)){for(var y,k=v.layerNumber,w=[],C=0,x=v.codeblocks.length;C<x;C++){y=v.codeblocks[C];var S,A=y.precinct,I=y.cbx-A.cbxMin,B=y.cby-A.cbyMin,O=!1,P=!1;if(void 0!==y.included)O=!!i(1);else{A=y.precinct;var M,E;if(void 0!==A.inclusionTree)M=A.inclusionTree;else{var L=A.cbxMax-A.cbxMin+1,D=A.cbyMax-A.cbyMin+1;M=new T(L,D,k);E=new R(L,D);A.inclusionTree=M;A.zeroBitPlanesTree=E}if(M.reset(I,B,k))for(;;){if(!i(1)){M.incrementValue(k);break}S=!M.nextLevel();if(S){y.included=!0;O=P=!0;break}}}if(O){if(P){E=A.zeroBitPlanesTree;E.reset(I,B);for(;;)if(i(1)){S=!E.nextLevel();if(S)break}else E.incrementValue();y.zeroBitPlanes=E.value}for(var F=function(){if(0===i(1))return 1;if(0===i(1))return 2;var e=i(2);if(e<3)return e+3;e=i(5);if(e<31)return e+6;e=i(7);return e+37}();i(1);)y.Lblock++;var q=c(F),U=(F<1<<q?q-1:q)+y.Lblock,N=i(U);w.push({codeblock:y,codingpasses:F,dataLength:N})}}o();m&&n(146);for(;w.length>0;){var j=w.shift();y=j.codeblock;void 0===y.data&&(y.data=[]);y.data.push({data:t,start:a+h,end:a+h+j.dataLength,codingpasses:j.codingpasses});h+=j.dataLength}}}return h}function x(e,t,a,r,i,n,s,o){for(var c=r.tbx0,l=r.tby0,h=r.tbx1-r.tbx0,f=r.codeblocks,d="H"===r.type.charAt(0)?1:0,g="H"===r.type.charAt(1)?t:0,p=0,m=f.length;p<m;++p){var b=f[p],v=b.tbx1_-b.tbx0_,y=b.tby1_-b.tby0_;if(0!==v&&0!==y&&void 0!==b.data){var k,w;k=new O(v,y,b.subbandType,b.zeroBitPlanes,n);w=2;var C,x,S,A=b.data,I=0,B=0;for(C=0,x=A.length;C<x;C++){S=A[C];I+=S.end-S.start;B+=S.codingpasses}var R=new Uint8Array(I),T=0;for(C=0,x=A.length;C<x;C++){S=A[C];var P=S.data.subarray(S.start,S.end);R.set(P,T);T+=P.length}var M=new u(R,0,I);k.setDecoder(M);for(C=0;C<B;C++){switch(w){case 0:k.runSignificancePropagationPass();break;case 1:k.runMagnitudeRefinementPass();break;case 2:k.runCleanupPass();o&&k.checkSegmentationSymbol()}w=(w+1)%3}var E,L,D,F=b.tbx0_-c+(b.tby0_-l)*h,q=k.coefficentsSign,U=k.coefficentsMagnitude,N=k.bitsDecoded,j=s?0:.5;T=0;var _="LL"!==r.type;for(C=0;C<y;C++){var z=F/h|0,H=2*z*(t-h)+d+g;for(E=0;E<v;E++){L=U[T];if(0!==L){L=(L+j)*i;0!==q[T]&&(L=-L);D=N[T];var G=_?H+(F<<1):F;e[G]=s&&D>=n?L:L*(1<<n-D)}F++;T++}F+=h-v}}}}function S(e,t,a){for(var r=t.components[a],i=r.codingStyleParameters,n=r.quantizationParameters,s=i.decompositionLevelsCount,o=n.SPqcds,c=n.scalarExpounded,l=n.guardBits,h=i.segmentationSymbolUsed,u=e.components[a].precision,f=i.reversibleTransformation,d=f?new E:new M,g=[],p=0,m=0;m<=s;m++){for(var b=r.resolutions[m],v=b.trx1-b.trx0,y=b.try1-b.try0,k=new Float32Array(v*y),w=0,C=b.subbands.length;w<C;w++){var S,A;if(c){S=o[p].mu;A=o[p].epsilon;p++}else{S=o[0].mu;A=o[0].epsilon+(m>0?1-m:0)}var I=b.subbands[w],R=B[I.type];x(k,v,y,I,f?1:Math.pow(2,u+R-A)*(1+S/2048),l+A-1,f,h)}g.push({width:v,height:y,items:k})}var T=d.calculate(g,r.tcx0,r.tcy0);return{left:r.tcx0,top:r.tcy0,width:T.width,height:T.height,items:T.items}}function A(e){for(var t=e.SIZ,a=e.components,r=t.Csiz,i=[],n=0,s=e.tiles.length;n<s;n++){var o,c=e.tiles[n],l=[];for(o=0;o<r;o++)l[o]=S(e,c,o);var h,u,f,d,g,p,m,b,v,y,k,w,C,x,A,I=l[0],B=new Uint8Array(I.items.length*r),R={left:I.left,top:I.top,width:I.width,height:I.height,items:B},T=0;if(c.codingStyleDefaultParameters.multipleComponentTransform){var O=4===r,P=l[0].items,M=l[1].items,E=l[2].items,L=O?l[3].items:null;h=a[0].precision-8;u=.5+(128<<h);f=255*(1<<h);g=.5*f;d=-g;var D=c.components[0],F=r-3;m=P.length;if(D.codingStyleParameters.reversibleTransformation)for(p=0;p<m;p++,T+=F){b=P[p]+u;v=M[p];y=E[p];w=b-(y+v>>2);k=w+y;C=w+v;B[T++]=k<=0?0:k>=f?255:k>>h;B[T++]=w<=0?0:w>=f?255:w>>h;B[T++]=C<=0?0:C>=f?255:C>>h}else for(p=0;p<m;p++,T+=F){b=P[p]+u;v=M[p];y=E[p];k=b+1.402*y;w=b-.34413*v-.71414*y;C=b+1.772*v;B[T++]=k<=0?0:k>=f?255:k>>h;B[T++]=w<=0?0:w>=f?255:w>>h;B[T++]=C<=0?0:C>=f?255:C>>h}if(O)for(p=0,T=3;p<m;p++,T+=4){x=L[p];B[T]=x<=d?0:x>=g?255:x+u>>h}}else for(o=0;o<r;o++){var q=l[o].items;h=a[o].precision-8;u=.5+(128<<h);f=127.5*(1<<h);d=-f;for(T=o,p=0,m=q.length;p<m;p++){A=q[p];B[T]=A<=d?0:A>=f?255:A+u>>h;T+=r}}i.push(R)}return i}function I(e,t){for(var a=e.SIZ,r=a.Csiz,i=e.tiles[t],n=0;n<r;n++){var s=i.components[n],o=void 0!==e.currentTile.QCC[n]?e.currentTile.QCC[n]:e.currentTile.QCD;s.quantizationParameters=o;var c=void 0!==e.currentTile.COC[n]?e.currentTile.COC[n]:e.currentTile.COD;s.codingStyleParameters=c}i.codingStyleDefaultParameters=e.currentTile.COD}var B={LL:0,LH:1,HL:1,HH:2};e.prototype={parse:function(e){if(65359!==l(e,0))for(var t=0,a=e.length;t<a;){var r=8,i=h(e,t),c=h(e,t+4);t+=r;if(1===i){i=4294967296*h(e,t)+h(e,t+4);t+=8;r+=8}0===i&&(i=a-t+r);i<r&&o("JPX Error: Invalid box field size");var u=i-r,f=!0;switch(c){case 1785737832:f=!1;break;case 1668246642:var d=e[t];if(1===d){var g=h(e,t+3);switch(g){case 16:case 17:case 18:break;default:s("Unknown colorspace "+g)}}else 2===d&&n("ICC profile not supported");break;case 1785737827:this.parseCodestream(e,t,t+u);break;case 1783636e3:218793738!==h(e,t)&&s("Invalid JP2 signature");break;case 1783634458:case 1718909296:case 1920099697:case 1919251232:case 1768449138:break;default:var p=String.fromCharCode(c>>24&255,c>>16&255,c>>8&255,255&c);s("Unsupported header type "+c+" ("+p+")")}f&&(t+=u)}else this.parseCodestream(e,0,e.length)},parseImageProperties:function(e){for(var t=e.getByte();t>=0;){var a=t;t=e.getByte();if(65361===(a<<8|t)){e.skip(4);var r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.getInt32()>>>0,s=e.getInt32()>>>0;e.skip(16);var c=e.getUint16();this.width=r-n;this.height=i-s;this.componentsCount=c;this.bitsPerComponent=8;return}}o("JPX Error: No size marker found in JPX stream")},parseCodestream:function(e,r,i){var n={},c=!1;try{for(var u=r;u+1<i;){var f=l(e,u);u+=2;var d,g,p,m,b,v,y=0;switch(f){case 65359:n.mainHeader=!0;break;case 65497:break;case 65361:y=l(e,u);var k={};k.Xsiz=h(e,u+4);k.Ysiz=h(e,u+8);k.XOsiz=h(e,u+12);k.YOsiz=h(e,u+16);k.XTsiz=h(e,u+20);k.YTsiz=h(e,u+24);k.XTOsiz=h(e,u+28);k.YTOsiz=h(e,u+32);var x=l(e,u+36);k.Csiz=x;var S=[];d=u+38;for(var B=0;B<x;B++){var R={precision:1+(127&e[d]),isSigned:!!(128&e[d]),XRsiz:e[d+1],YRsiz:e[d+1]};t(R,k);S.push(R)}n.SIZ=k;n.components=S;a(n,S);n.QCC=[];n.COC=[];break;case 65372:y=l(e,u);var T={};d=u+2;g=e[d++];switch(31&g){case 0:m=8;b=!0;break;case 1:m=16;b=!1;break;case 2:m=16;b=!0;break;default:throw new Error("Invalid SQcd value "+g)}T.noQuantization=8===m;T.scalarExpounded=b;T.guardBits=g>>5;p=[];for(;d<y+u;){var O={};if(8===m){O.epsilon=e[d++]>>3;O.mu=0}else{O.epsilon=e[d]>>3;O.mu=(7&e[d])<<8|e[d+1];d+=2}p.push(O)}T.SPqcds=p;if(n.mainHeader)n.QCD=T;else{n.currentTile.QCD=T;n.currentTile.QCC=[]}break;case 65373:y=l(e,u);var P={};d=u+2;var M;if(n.SIZ.Csiz<257)M=e[d++];else{M=l(e,d);d+=2}g=e[d++];switch(31&g){case 0:m=8;b=!0;break;case 1:m=16;b=!1;break;case 2:m=16;b=!0;break;default:throw new Error("Invalid SQcd value "+g)}P.noQuantization=8===m;P.scalarExpounded=b;P.guardBits=g>>5;p=[];for(;d<y+u;){O={};if(8===m){O.epsilon=e[d++]>>3;O.mu=0}else{O.epsilon=e[d]>>3;O.mu=(7&e[d])<<8|e[d+1];d+=2}p.push(O)}P.SPqcds=p;n.mainHeader?n.QCC[M]=P:n.currentTile.QCC[M]=P;break;case 65362:y=l(e,u);var E={};d=u+2;var L=e[d++];E.entropyCoderWithCustomPrecincts=!!(1&L);E.sopMarkerUsed=!!(2&L);E.ephMarkerUsed=!!(4&L);E.progressionOrder=e[d++];E.layersCount=l(e,d);d+=2;E.multipleComponentTransform=e[d++];E.decompositionLevelsCount=e[d++];E.xcb=2+(15&e[d++]);E.ycb=2+(15&e[d++]);var D=e[d++];E.selectiveArithmeticCodingBypass=!!(1&D);E.resetContextProbabilities=!!(2&D);E.terminationOnEachCodingPass=!!(4&D);E.verticalyStripe=!!(8&D);E.predictableTermination=!!(16&D);E.segmentationSymbolUsed=!!(32&D);E.reversibleTransformation=e[d++];if(E.entropyCoderWithCustomPrecincts){for(var F=[];d<y+u;){var q=e[d++];F.push({PPx:15&q,PPy:q>>4})}E.precinctsSizes=F}var U=[];E.selectiveArithmeticCodingBypass&&U.push("selectiveArithmeticCodingBypass");E.resetContextProbabilities&&U.push("resetContextProbabilities");E.terminationOnEachCodingPass&&U.push("terminationOnEachCodingPass");E.verticalyStripe&&U.push("verticalyStripe");E.predictableTermination&&U.push("predictableTermination");if(U.length>0){c=!0;throw new Error("Unsupported COD options ("+U.join(", ")+")")}if(n.mainHeader)n.COD=E;else{n.currentTile.COD=E;n.currentTile.COC=[]}break;case 65424:y=l(e,u);v={};v.index=l(e,u+2);v.length=h(e,u+4);v.dataEnd=v.length+u-2;v.partIndex=e[u+8];v.partsCount=e[u+9];n.mainHeader=!1;if(0===v.partIndex){v.COD=n.COD;v.COC=n.COC.slice(0);v.QCD=n.QCD;v.QCC=n.QCC.slice(0)}n.currentTile=v;break;case 65427:v=n.currentTile;if(0===v.partIndex){I(n,v.index);w(n)}y=v.dataEnd-u;C(n,e,u,y);break;case 65365:case 65367:case 65368:case 65380:y=l(e,u);break;case 65363:throw new Error("Codestream code 0xFF53 (COC) is not implemented");default:throw new Error("Unknown codestream code: "+f.toString(16))}u+=y}}catch(e){c||this.failOnCorruptedImage?o("JPX Error: "+e.message):s("JPX: Trying to recover from: "+e.message)}this.tiles=A(n);this.width=n.SIZ.Xsiz-n.SIZ.XOsiz;this.height=n.SIZ.Ysiz-n.SIZ.YOsiz;this.componentsCount=n.SIZ.Csiz}};var R=function(){function e(e,t){var a=c(Math.max(e,t))+1;this.levels=[];for(var r=0;r<a;r++){var i={width:e,height:t,items:[]};this.levels.push(i);e=Math.ceil(e/2);t=Math.ceil(t/2)}}e.prototype={reset:function(e,t){for(var a,r=0,i=0;r<this.levels.length;){a=this.levels[r];var n=e+t*a.width;if(void 0!==a.items[n]){i=a.items[n];break}a.index=n;e>>=1;t>>=1;r++}r--;a=this.levels[r];a.items[a.index]=i;this.currentLevel=r;delete this.value},incrementValue:function(){var e=this.levels[this.currentLevel];e.items[e.index]++},nextLevel:function(){var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];e--;if(e<0){this.value=a;return!1}this.currentLevel=e;t=this.levels[e];t.items[t.index]=a;return!0}};return e}(),T=function(){function e(e,t,a){var r=c(Math.max(e,t))+1;this.levels=[];for(var i=0;i<r;i++){for(var n=new Uint8Array(e*t),s=0,o=n.length;s<o;s++)n[s]=a;var l={width:e,height:t,items:n};this.levels.push(l);e=Math.ceil(e/2);t=Math.ceil(t/2)}}e.prototype={reset:function(e,t,a){for(var r=0;r<this.levels.length;){var i=this.levels[r],n=e+t*i.width;i.index=n;var s=i.items[n];if(255===s)break;if(s>a){this.currentLevel=r;this.propagateValues();return!1}e>>=1;t>>=1;r++}this.currentLevel=r-1;return!0},incrementValue:function(e){var t=this.levels[this.currentLevel];t.items[t.index]=e+1;this.propagateValues()},propagateValues:function(){for(var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];--e>=0;){t=this.levels[e];t.items[t.index]=a}},nextLevel:function(){var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];t.items[t.index]=255;e--;if(e<0)return!1;this.currentLevel=e;t=this.levels[e];t.items[t.index]=a;return!0}};return e}(),O=function(){function e(e,i,n,s,o){this.width=e;this.height=i;this.contextLabelTable="HH"===n?r:"HL"===n?a:t;var c=e*i;this.neighborsSignificance=new Uint8Array(c);this.coefficentsSign=new Uint8Array(c);this.coefficentsMagnitude=o>14?new Uint32Array(c):o>6?new Uint16Array(c):new Uint8Array(c);this.processingFlags=new Uint8Array(c);var l=new Uint8Array(c);if(0!==s)for(var h=0;h<c;h++)l[h]=s;this.bitsDecoded=l;this.reset()}var t=new Uint8Array([0,5,8,0,3,7,8,0,4,7,8,0,0,0,0,0,1,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8]),a=new Uint8Array([0,3,4,0,5,7,7,0,8,8,8,0,0,0,0,0,1,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8]),r=new Uint8Array([0,1,2,0,1,2,2,0,2,2,2,0,0,0,0,0,3,4,5,0,4,5,5,0,5,5,5,0,0,0,0,0,6,7,7,0,7,7,7,0,7,7,7,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8]);e.prototype={setDecoder:function(e){this.decoder=e},reset:function(){this.contexts=new Int8Array(19);this.contexts[0]=8;this.contexts[17]=92;this.contexts[18]=6},setNeighborsSignificance:function(e,t,a){var r,i=this.neighborsSignificance,n=this.width,s=this.height,o=t>0,c=t+1<n;if(e>0){r=a-n;o&&(i[r-1]+=16);c&&(i[r+1]+=16);i[r]+=4}if(e+1<s){r=a+n;o&&(i[r-1]+=16);c&&(i[r+1]+=16);i[r]+=4}o&&(i[a-1]+=1);c&&(i[a+1]+=1);i[a]|=128},runSignificancePropagationPass:function(){for(var e=this.decoder,t=this.width,a=this.height,r=this.coefficentsMagnitude,i=this.coefficentsSign,n=this.neighborsSignificance,s=this.processingFlags,o=this.contexts,c=this.contextLabelTable,l=this.bitsDecoded,h=0;h<a;h+=4)for(var u=0;u<t;u++)for(var f=h*t+u,d=0;d<4;d++,f+=t){var g=h+d;if(g>=a)break;s[f]&=-2;if(!r[f]&&n[f]){var p=c[n[f]],m=e.readBit(o,p);if(m){var b=this.decodeSignBit(g,u,f);i[f]=b;r[f]=1;this.setNeighborsSignificance(g,u,f);s[f]|=2}l[f]++;s[f]|=1}}},decodeSignBit:function(e,t,a){var r,i,n,s,o,c,l=this.width,h=this.height,u=this.coefficentsMagnitude,f=this.coefficentsSign;s=t>0&&0!==u[a-1];if(t+1<l&&0!==u[a+1]){n=f[a+1];if(s){i=f[a-1];r=1-n-i}else r=1-n-n}else if(s){i=f[a-1];r=1-i-i}else r=0;var d=3*r;s=e>0&&0!==u[a-l];if(e+1<h&&0!==u[a+l]){n=f[a+l];if(s){i=f[a-l];r=1-n-i+d}else r=1-n-n+d}else if(s){i=f[a-l];r=1-i-i+d}else r=d;if(r>=0){o=9+r;c=this.decoder.readBit(this.contexts,o)}else{o=9-r;c=1^this.decoder.readBit(this.contexts,o)}return c},runMagnitudeRefinementPass:function(){for(var e,t=this.decoder,a=this.width,r=this.height,i=this.coefficentsMagnitude,n=this.neighborsSignificance,s=this.contexts,o=this.bitsDecoded,c=this.processingFlags,l=a*r,h=4*a,u=0;u<l;u=e){e=Math.min(l,u+h);for(var f=0;f<a;f++)for(var d=u+f;d<e;d+=a)if(i[d]&&0==(1&c[d])){var g=16;if(0!=(2&c[d])){c[d]^=2;var p=127&n[d];g=0===p?15:14}var m=t.readBit(s,g);i[d]=i[d]<<1|m;o[d]++;c[d]|=1}}},runCleanupPass:function(){for(var e,t=this.decoder,a=this.width,r=this.height,i=this.neighborsSignificance,n=this.coefficentsMagnitude,s=this.coefficentsSign,o=this.contexts,c=this.contextLabelTable,l=this.bitsDecoded,h=this.processingFlags,u=a,f=2*a,d=3*a,g=0;g<r;g=e){e=Math.min(g+4,r);for(var p=g*a,m=g+3<r,b=0;b<a;b++){var v,y=p+b,k=m&&0===h[y]&&0===h[y+u]&&0===h[y+f]&&0===h[y+d]&&0===i[y]&&0===i[y+u]&&0===i[y+f]&&0===i[y+d],w=0,C=y,x=g;if(k){if(!t.readBit(o,18)){l[y]++;l[y+u]++;l[y+f]++;l[y+d]++;continue}w=t.readBit(o,17)<<1|t.readBit(o,17);if(0!==w){x=g+w;C+=w*a}v=this.decodeSignBit(x,b,C);s[C]=v;n[C]=1;this.setNeighborsSignificance(x,b,C);h[C]|=2;C=y;for(var S=g;S<=x;S++,C+=a)l[C]++;w++}for(x=g+w;x<e;x++,C+=a)if(!n[C]&&0==(1&h[C])){var A=c[i[C]],I=t.readBit(o,A);if(1===I){v=this.decodeSignBit(x,b,C);s[C]=v;n[C]=1;this.setNeighborsSignificance(x,b,C);h[C]|=2}l[C]++}}}},checkSegmentationSymbol:function(){var e=this.decoder,t=this.contexts;10!=(e.readBit(t,17)<<3|e.readBit(t,17)<<2|e.readBit(t,17)<<1|e.readBit(t,17))&&o("JPX Error: Invalid segmentation symbol")}};return e}(),P=function(){function e(){}e.prototype.calculate=function(e,t,a){for(var r=e[0],i=1,n=e.length;i<n;i++)r=this.iterate(r,e[i],t,a);return r};e.prototype.extend=function(e,t,a){var r=t-1,i=t+1,n=t+a-2,s=t+a;e[r--]=e[i++];e[s++]=e[n--];e[r--]=e[i++];e[s++]=e[n--];e[r--]=e[i++];e[s++]=e[n--];e[r]=e[i];e[s]=e[n]};e.prototype.iterate=function(e,t,a,r){var i,n,s,o,c,l,h=e.width,u=e.height,f=e.items,d=t.width,g=t.height,p=t.items;for(s=0,i=0;i<u;i++){o=2*i*d;for(n=0;n<h;n++,s++,o+=2)p[o]=f[s]}f=e.items=null;var m=new Float32Array(d+8);if(1===d){if(0!=(1&a))for(l=0,s=0;l<g;l++,s+=d)p[s]*=.5}else for(l=0,s=0;l<g;l++,s+=d){m.set(p.subarray(s,s+d),4);this.extend(m,4,d);this.filter(m,4,d);p.set(m.subarray(4,4+d),s)}var b=16,v=[];for(i=0;i<b;i++)v.push(new Float32Array(g+8));var y,k=0;e=4+g;if(1===g){if(0!=(1&r))for(c=0;c<d;c++)p[c]*=.5}else for(c=0;c<d;c++){if(0===k){b=Math.min(d-c,b);for(s=c,o=4;o<e;s+=d,o++)for(y=0;y<b;y++)v[y][o]=p[s+y];k=b}k--;var w=v[k];this.extend(w,4,g);this.filter(w,4,g);if(0===k){s=c-b+1;for(o=4;o<e;s+=d,o++)for(y=0;y<b;y++)p[s+y]=v[y][o]}}return{width:d,height:g,items:p}};return e}(),M=function(){function e(){P.call(this)}e.prototype=Object.create(P.prototype);e.prototype.filter=function(e,t,a){var r=a>>1;t|=0;var i,n,s,o,c=-1.586134342059924,l=-.052980118572961,h=.882911075530934,u=.443506852043971,f=1.230174104914001;i=t-3;for(n=r+4;n--;i+=2)e[i]*=1/f;i=t-2;s=u*e[i-1];for(n=r+3;n--;i+=2){o=u*e[i+1];e[i]=f*e[i]-s-o;if(!n--)break;i+=2;s=u*e[i+1];e[i]=f*e[i]-s-o}i=t-1;s=h*e[i-1];for(n=r+2;n--;i+=2){o=h*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=h*e[i+1];e[i]-=s+o}i=t;s=l*e[i-1];for(n=r+1;n--;i+=2){o=l*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=l*e[i+1];e[i]-=s+o}if(0!==r){i=t+1;s=c*e[i-1];for(n=r;n--;i+=2){o=c*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=c*e[i+1];e[i]-=s+o}}};return e}(),E=function(){function e(){P.call(this)}e.prototype=Object.create(P.prototype);e.prototype.filter=function(e,t,a){var r=a>>1;t|=0;var i,n;for(i=t,n=r+1;n--;i+=2)e[i]-=e[i-1]+e[i+1]+2>>2;for(i=t+1,n=r;n--;i+=2)e[i]+=e[i-1]+e[i+1]>>1};return e}();return e}();t.JpxImage=f},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(13),s=a(5),o=a(12),c=a(3),l=r.InvalidPDFException,h=r.MissingDataException,u=r.XRefParseException,f=r.assert,d=r.bytesToString,g=r.createPromiseCapability,p=r.error,m=r.info,b=r.isArray,v=r.isBool,y=r.isInt,k=r.isString,w=r.shadow,C=r.stringToPDFString,x=r.stringToUTF8String,S=r.warn,A=r.createValidAbsoluteUrl,I=r.Util,B=i.Dict,R=i.Ref,T=i.RefSet,O=i.RefSetCache,P=i.isName,M=i.isCmd,E=i.isDict,L=i.isRef,D=i.isRefsEqual,F=i.isStream,q=n.CipherTransformFactory,U=s.Lexer,N=s.Parser,j=o.ChunkedStream,_=c.ColorSpace,z=function(){function e(e,t,a){this.pdfManager=e;this.xref=t;this.catDict=t.getCatalogObj();f(E(this.catDict),"catalog object is not a dictionary");this.fontCache=new O;this.builtInCMapCache=Object.create(null);this.pageKidsCountCache=new O;this.pageFactory=a;this.pagePromises=[]}e.prototype={get metadata(){var e=this.catDict.getRaw("Metadata");if(!L(e))return w(this,"metadata",null);var t,a=!!this.xref.encrypt&&this.xref.encrypt.encryptMetadata,r=this.xref.fetch(e,!a);if(r&&E(r.dict)){var i=r.dict.get("Type"),n=r.dict.get("Subtype");if(P(i,"Metadata")&&P(n,"XML"))try{t=x(d(r.getBytes()))}catch(e){if(e instanceof h)throw e;m("Skipping invalid metadata.")}}return w(this,"metadata",t)},get toplevelPagesDict(){var e=this.catDict.get("Pages");f(E(e),"invalid top-level pages dictionary");return w(this,"toplevelPagesDict",e)},get documentOutline(){var e=null;try{e=this.readDocumentOutline()}catch(e){if(e instanceof h)throw e;S("Unable to read document outline")}return w(this,"documentOutline",e)},readDocumentOutline:function(){var t=this.catDict.get("Outlines");if(!E(t))return null;t=t.getRaw("First");if(!L(t))return null;var a={items:[]},r=[{obj:t,parent:a}],i=new T;i.put(t);for(var n=this.xref,s=new Uint8Array(3);r.length>0;){var o=r.shift(),c=n.fetchIfRef(o.obj);if(null!==c){f(c.has("Title"),"Invalid outline item");var l={url:null,dest:null};e.parseDestDictionary({destDict:c,resultObj:l,docBaseUrl:this.pdfManager.docBaseUrl});var h=c.get("Title"),u=c.get("F")||0,d=c.getArray("C"),g=s;!b(d)||3!==d.length||0===d[0]&&0===d[1]&&0===d[2]||(g=_.singletons.rgb.getRgb(d,0));var p={dest:l.dest,url:l.url,unsafeUrl:l.unsafeUrl,newWindow:l.newWindow,title:C(h),color:g,count:c.get("Count"),bold:!!(2&u),italic:!!(1&u),items:[]};o.parent.items.push(p);t=c.getRaw("First");if(L(t)&&!i.has(t)){r.push({obj:t,parent:p});i.put(t)}t=c.getRaw("Next");if(L(t)&&!i.has(t)){r.push({obj:t,parent:o.parent});i.put(t)}}}return a.items.length>0?a.items:null},get numPages(){var e=this.toplevelPagesDict.get("Count");f(y(e),"page count in top level pages object is not an integer");return w(this,"num",e)},get destinations(){function e(e){return E(e)?e.get("D"):e}var t,a,r=this.xref,i={},n=this.catDict.get("Names");n&&n.has("Dests")?t=n.getRaw("Dests"):this.catDict.has("Dests")&&(a=this.catDict.get("Dests"));if(a){n=a;n.forEach(function(t,a){a&&(i[t]=e(a))})}if(t){var s=new X(t,r),o=s.getAll();for(var c in o)i[c]=e(o[c])}return w(this,"destinations",i)},getDestination:function(e){function t(e){return E(e)?e.get("D"):e}var a,r,i=this.xref,n=null,s=this.catDict.get("Names");s&&s.has("Dests")?a=s.getRaw("Dests"):this.catDict.has("Dests")&&(r=this.catDict.get("Dests"));if(r){var o=r.get(e);o&&(n=t(o))}if(a){n=t(new X(a,i).get(e))}return n},get pageLabels(){var e=null;try{e=this.readPageLabels()}catch(e){if(e instanceof h)throw e;S("Unable to read page labels.")}return w(this,"pageLabels",e)},readPageLabels:function(){var e=this.catDict.getRaw("PageLabels");if(!e)return null;for(var t=new Array(this.numPages),a=null,r="",i=new V(e,this.xref),n=i.getAll(),s="",o=1,c=0,l=this.numPages;c<l;c++){if(c in n){var h=n[c];f(E(h),"The PageLabel is not a dictionary.");var u=h.get("Type");f(!u||P(u,"PageLabel"),"Invalid type in PageLabel dictionary.");var d=h.get("S");f(!d||P(d),"Invalid style in PageLabel dictionary.");a=d?d.name:null;var g=h.get("P");f(!g||k(g),"Invalid prefix in PageLabel dictionary.");r=g?C(g):"";var p=h.get("St");f(!p||y(p)&&p>=1,"Invalid start in PageLabel dictionary.");o=p||1}switch(a){case"D":s=o;break;case"R":case"r":s=I.toRoman(o,"r"===a);break;case"A":case"a":for(var m="a"===a?97:65,b=o-1,v=String.fromCharCode(m+b%26),w=[],x=0,S=b/26|0;x<=S;x++)w.push(v);s=w.join("");break;default:f(!a,'Invalid style "'+a+'" in PageLabel dictionary.')}t[c]=r+s;s="";o++}return t},get attachments(){var e,t=this.xref,a=null,r=this.catDict.get("Names");r&&(e=r.getRaw("EmbeddedFiles"));if(e){var i=new X(e,t),n=i.getAll();for(var s in n){var o=new W(n[s],t);a||(a=Object.create(null));a[C(s)]=o.serializable}}return w(this,"attachments",a)},get javaScript(){function e(e){var t=e.get("S");if(P(t,"JavaScript")){var a=e.get("JS");if(F(a))a=d(a.getBytes());else if(!k(a))return;r.push(C(a))}}var t=this.xref,a=this.catDict.get("Names"),r=[];if(a&&a.has("JavaScript")){var i=new X(a.getRaw("JavaScript"),t),n=i.getAll();for(var s in n){var o=n[s];E(o)&&e(o)}}var c=this.catDict.get("OpenAction");if(E(c,"Action")){var l=c.get("S");if(P(l,"Named")){var h=c.get("N");P(h,"Print")&&r.push("print({});")}else e(c)}return w(this,"javaScript",r)},cleanup:function(){this.pageKidsCountCache.clear();var e=[];this.fontCache.forEach(function(t){e.push(t)});return Promise.all(e).then(function(e){for(var t=0,a=e.length;t<a;t++){delete e[t].dict.translated}this.fontCache.clear();this.builtInCMapCache=Object.create(null)}.bind(this))},getPage:function(e){e in this.pagePromises||(this.pagePromises[e]=this.getPageDict(e).then(function(t){var a=t[0],r=t[1];return this.pageFactory.createPage(e,a,r,this.fontCache,this.builtInCMapCache)}.bind(this)));return this.pagePromises[e]},getPageDict:function(e){function t(){for(;i.length;){var c=i.pop();if(L(c)){a=o.get(c);if(a>0&&n+a<e){n+=a;continue}s.fetchAsync(c).then(function(a){if(E(a,"Page")||E(a)&&!a.has("Kids"))if(e===n){c&&!o.has(c)&&o.put(c,1);r.resolve([a,c])}else{n++;t()}else{i.push(a);t()}},r.reject);return}f(E(c),"page dictionary kid reference points to wrong type of object");a=c.get("Count");var l=c.objId;l&&!o.has(l)&&o.put(l,a);if(n+a<=e)n+=a;else{var h=c.get("Kids");f(b(h),"page dictionary kids object is not an array");for(var u=h.length-1;u>=0;u--)i.push(h[u])}}r.reject("Page index "+e+" not found.")}var a,r=g(),i=[this.catDict.getRaw("Pages")],n=0,s=this.xref,o=this.pageKidsCountCache;t();return r.promise},getPageIndex:function(e){function t(t){var a,i=0;return r.fetchAsync(t).then(function(r){if(D(t,e)&&!E(r,"Page")&&(!E(r)||r.has("Type")||!r.has("Contents")))throw new Error("The reference does not point to a /Page Dict.");if(!r)return null;f(E(r),"node must be a Dict.");a=r.getRaw("Parent");return r.getAsync("Parent")}).then(function(e){if(!e)return null;f(E(e),"parent must be a Dict.");return e.getAsync("Kids")}).then(function(e){if(!e)return null;for(var n=[],s=!1,o=0;o<e.length;o++){var c=e[o];f(L(c),"kid must be a Ref.");if(c.num===t.num){s=!0;break}n.push(r.fetchAsync(c).then(function(e){if(e.has("Count")){var t=e.get("Count");i+=t}else i++}))}s||p("kid ref not found in parents kids");return Promise.all(n).then(function(){return[i,a]})})}function a(e){return t(e).then(function(e){if(!e)return i;var t=e[0],r=e[1];i+=t;return a(r)})}var r=this.xref,i=0;return a(e)}};e.parseDestDictionary=function(e){var t=e.destDict;if(E(t)){var a=e.resultObj;if("object"==typeof a){var r,i,n=e.docBaseUrl||null,s=t.get("A");if(E(s)){var o=s.get("S").name;switch(o){case"URI":r=s.get("URI");P(r)?r="/"+r.name:k(r)&&(r=function(e){return 0===e.indexOf("www.")?"http://"+e:e}(r));break;case"GoTo":i=s.get("D");break;case"Launch":case"GoToR":var c=s.get("F");E(c)?r=c.get("F")||null:k(c)&&(r=c);var l=s.get("D");if(l){P(l)&&(l=l.name);if(k(r)){var h=r.split("#")[0];k(l)?r=h+"#"+(/^\d+$/.test(l)?"nameddest=":"")+l:b(l)&&(r=h+"#"+JSON.stringify(l))}}var u=s.get("NewWindow");v(u)&&(a.newWindow=u);break;case"Named":var f=s.get("N");P(f)&&(a.action=f.name);break;case"JavaScript":var g,p=s.get("JS");F(p)?g=d(p.getBytes()):k(p)&&(g=p);if(g){var m=["app.launchURL","window.open"],y=new RegExp("^\\s*("+m.join("|").split(".").join("\\.")+")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))","i"),w=y.exec(C(g));if(w&&w[2]){r=w[2];"true"===w[3]&&"app.launchURL"===w[1]&&(a.newWindow=!0);break}}default:S('Catalog_parseDestDictionary: Unrecognized link type "'+o+'".')}}else t.has("Dest")&&(i=t.get("Dest"));if(k(r)){r=function(e){try{return x(e)}catch(t){return e}}(r);var I=A(r,n) +;I&&(a.url=I.href);a.unsafeUrl=r}if(i){P(i)&&(i=i.name);(k(i)||b(i))&&(a.dest=i)}}else S('Catalog_parseDestDictionary: "resultObj" must be an object.')}else S('Catalog_parseDestDictionary: "destDict" must be a dictionary.')};return e}(),H=function(){function e(e,t){this.stream=e;this.pdfManager=t;this.entries=[];this.xrefstms=Object.create(null);this.cache=[];this.stats={streamTypes:[],fontTypes:[]}}e.prototype={setStartXRef:function(e){this.startXRefQueue=[e]},parse:function(e){var t;if(e){S("Indexing all PDF objects");t=this.indexObjects()}else t=this.readXRef();t.assignXref(this);this.trailer=t;var a=t.get("Encrypt");if(E(a)){var r=t.get("ID"),i=r&&r.length?r[0]:"";a.suppressEncryption=!0;this.encrypt=new q(a,i,this.pdfManager.password)}(this.root=t.get("Root"))||p("Invalid root reference")},processXRefTable:function(e){"tableState"in this||(this.tableState={entryNum:0,streamPos:e.lexer.stream.pos,parserBuf1:e.buf1,parserBuf2:e.buf2});var t=this.readXRefTable(e);M(t,"trailer")||p("Invalid XRef table: could not find trailer dictionary");var a=e.getObj();!E(a)&&a.dict&&(a=a.dict);E(a)||p("Invalid XRef table: could not parse trailer dictionary");delete this.tableState;return a},readXRefTable:function(e){var t=e.lexer.stream,a=this.tableState;t.pos=a.streamPos;e.buf1=a.parserBuf1;e.buf2=a.parserBuf2;for(var r;;){if(!("firstEntryNum"in a&&"entryCount"in a)){if(M(r=e.getObj(),"trailer"))break;a.firstEntryNum=r;a.entryCount=e.getObj()}var i=a.firstEntryNum,n=a.entryCount;y(i)&&y(n)||p("Invalid XRef table: wrong types in subsection header");for(var s=a.entryNum;s<n;s++){a.streamPos=t.pos;a.entryNum=s;a.parserBuf1=e.buf1;a.parserBuf2=e.buf2;var o={};o.offset=e.getObj();o.gen=e.getObj();var c=e.getObj();M(c,"f")?o.free=!0:M(c,"n")&&(o.uncompressed=!0);y(o.offset)&&y(o.gen)&&(o.free||o.uncompressed)||p("Invalid entry in XRef subsection: "+i+", "+n);0===s&&o.free&&1===i&&(i=0);this.entries[s+i]||(this.entries[s+i]=o)}a.entryNum=0;a.streamPos=t.pos;a.parserBuf1=e.buf1;a.parserBuf2=e.buf2;delete a.firstEntryNum;delete a.entryCount}this.entries[0]&&!this.entries[0].free&&p("Invalid XRef table: unexpected first object");return r},processXRefStream:function(e){if(!("streamState"in this)){var t=e.dict,a=t.get("W"),r=t.get("Index");r||(r=[0,t.get("Size")]);this.streamState={entryRanges:r,byteWidths:a,entryNum:0,streamPos:e.pos}}this.readXRefStream(e);delete this.streamState;return e.dict},readXRefStream:function(e){var t,a,r=this.streamState;e.pos=r.streamPos;for(var i=r.byteWidths,n=i[0],s=i[1],o=i[2],c=r.entryRanges;c.length>0;){var l=c[0],h=c[1];y(l)&&y(h)||p("Invalid XRef range fields: "+l+", "+h);y(n)&&y(s)&&y(o)||p("Invalid XRef entry fields length: "+l+", "+h);for(t=r.entryNum;t<h;++t){r.entryNum=t;r.streamPos=e.pos;var u=0,f=0,d=0;for(a=0;a<n;++a)u=u<<8|e.getByte();0===n&&(u=1);for(a=0;a<s;++a)f=f<<8|e.getByte();for(a=0;a<o;++a)d=d<<8|e.getByte();var g={};g.offset=f;g.gen=d;switch(u){case 0:g.free=!0;break;case 1:g.uncompressed=!0;break;case 2:break;default:p("Invalid XRef entry type: "+u)}this.entries[l+t]||(this.entries[l+t]=g)}r.entryNum=0;r.streamPos=e.pos;c.splice(0,2)}},indexObjects:function(){function e(e,t,a){for(var r=a.length,i=e.length,n=0;t<i;){for(var s=0;s<r&&e[t+s]===a[s];)++s;if(s>=r)break;t++;n++}return n}var t=10,a=13,r=60,i=/^(\d+)\s+(\d+)\s+obj\b/,n=new Uint8Array([116,114,97,105,108,101,114]),s=new Uint8Array([115,116,97,114,116,120,114,101,102]),o=new Uint8Array([101,110,100,111,98,106]),c=new Uint8Array([47,88,82,101,102]);this.entries.length=0;var h=this.stream;h.pos=0;for(var u=h.getBytes(),f=h.start,d=u.length,g=[],p=[];f<d;){var m=u[f];if(9!==m&&m!==t&&m!==a&&32!==m)if(37!==m){var b,v=function(e,i){for(var n="",s=e[i];s!==t&&s!==a&&s!==r&&!(++i>=e.length);){n+=String.fromCharCode(s);s=e[i]}return n}(u,f);if(0!==v.indexOf("xref")||4!==v.length&&!/\s/.test(v[4]))if(b=i.exec(v)){void 0===this.entries[b[1]]&&(this.entries[b[1]]={offset:f-h.start,gen:0|b[2],uncompressed:!0});var y=e(u,f,o)+7,k=u.subarray(f,f+y),w=e(k,0,c);if(w<y&&k[w+5]<64){p.push(f-h.start);this.xrefstms[f-h.start]=1}f+=y}else if(0!==v.indexOf("trailer")||7!==v.length&&!/\s/.test(v[7]))f+=v.length+1;else{g.push(f);f+=e(u,f,s)}else{f+=e(u,f,n);g.push(f);f+=e(u,f,s)}}else do{++f;if(f>=d)break;m=u[f]}while(m!==t&&m!==a);else++f}var C,x;for(C=0,x=p.length;C<x;++C){this.startXRefQueue.push(p[C]);this.readXRef(!0)}var S;for(C=0,x=g.length;C<x;++C){h.pos=g[C];var A=new N(new U(h),!0,this,!0),I=A.getObj();if(M(I,"trailer")){S=A.getObj();if(E(S)&&S.has("ID"))return S}}if(S)return S;throw new l("Invalid PDF structure")},readXRef:function(e){var t=this.stream;try{for(;this.startXRefQueue.length;){var a=this.startXRefQueue[0];t.pos=a+t.start;var r,i=new N(new U(t),!0,this),n=i.getObj();if(M(n,"xref")){r=this.processXRefTable(i);this.topDict||(this.topDict=r);n=r.get("XRefStm");if(y(n)){var s=n;if(!(s in this.xrefstms)){this.xrefstms[s]=1;this.startXRefQueue.push(s)}}}else if(y(n)){y(i.getObj())&&M(i.getObj(),"obj")&&F(n=i.getObj())||p("Invalid XRef stream");r=this.processXRefStream(n);this.topDict||(this.topDict=r);r||p("Failed to read XRef stream")}else p("Invalid XRef stream header");n=r.get("Prev");y(n)?this.startXRefQueue.push(n):L(n)&&this.startXRefQueue.push(n.num);this.startXRefQueue.shift()}return this.topDict}catch(e){if(e instanceof h)throw e;m("(while reading XRef): "+e)}if(!e)throw new u},getEntry:function(e){var t=this.entries[e];return t&&!t.free&&t.offset?t:null},fetchIfRef:function(e,t){return L(e)?this.fetch(e,t):e},fetch:function(e,t){f(L(e),"ref object is not a reference");var a=e.num;if(a in this.cache){var r=this.cache[a];r instanceof B&&!r.objId&&(r.objId=e.toString());return r}var i=this.getEntry(a);if(null===i)return this.cache[a]=null;i=i.uncompressed?this.fetchUncompressed(e,i,t):this.fetchCompressed(i,t);E(i)?i.objId=e.toString():F(i)&&(i.dict.objId=e.toString());return i},fetchUncompressed:function(e,t,a){var r=e.gen,i=e.num;t.gen!==r&&p("inconsistent generation in XRef");var n=this.stream.makeSubStream(t.offset+this.stream.start),s=new N(new U(n),!0,this),o=s.getObj(),c=s.getObj(),l=s.getObj();y(o)&&parseInt(o,10)===i&&y(c)&&parseInt(c,10)===r&&M(l)||p("bad XRef entry");if(!M(l,"obj")){if(0===l.cmd.indexOf("obj")){i=parseInt(l.cmd.substring(3),10);if(!isNaN(i))return i}p("bad XRef entry")}t=this.encrypt&&!a?s.getObj(this.encrypt.createCipherTransform(i,r)):s.getObj();F(t)||(this.cache[i]=t);return t},fetchCompressed:function(e,t){var a=e.offset,r=this.fetch(new R(a,0));F(r)||p("bad ObjStm stream");var i=r.dict.get("First"),n=r.dict.get("N");y(i)&&y(n)||p("invalid first and n parameters for ObjStm stream");var s=new N(new U(r),!1,this);s.allowStreams=!0;var o,c,l=[],h=[];for(o=0;o<n;++o){c=s.getObj();y(c)||p("invalid object number in the ObjStm stream: "+c);h.push(c);var u=s.getObj();y(u)||p("invalid object offset in the ObjStm stream: "+u)}for(o=0;o<n;++o){l.push(s.getObj());M(s.buf1,"endobj")&&s.shift();c=h[o];var f=this.entries[c];f&&f.offset===a&&f.gen===o&&(this.cache[c]=l[o])}e=l[e.gen];void 0===e&&p("bad XRef entry for compressed object");return e},fetchIfRefAsync:function(e,t){return L(e)?this.fetchAsync(e,t):Promise.resolve(e)},fetchAsync:function(e,t){var a=this.stream.manager,r=this;return new Promise(function i(n,s){try{n(r.fetch(e,t))}catch(e){if(e instanceof h){a.requestRange(e.begin,e.end).then(function(){i(n,s)},s);return}s(e)}})},getCatalogObj:function(){return this.root}};return e}(),G=function(){function e(e,t){throw new Error("Cannot initialize NameOrNumberTree.")}e.prototype={getAll:function(){var e=Object.create(null);if(!this.root)return e;var t=this.xref,a=new T;a.put(this.root);for(var r=[this.root];r.length>0;){var i,n,s=t.fetchIfRef(r.shift());if(E(s))if(s.has("Kids")){var o=s.get("Kids");for(i=0,n=o.length;i<n;i++){var c=o[i];f(!a.has(c),'Duplicate entry in "'+this._type+'" tree.');r.push(c);a.put(c)}}else{var l=s.get(this._type);if(b(l))for(i=0,n=l.length;i<n;i+=2)e[t.fetchIfRef(l[i])]=t.fetchIfRef(l[i+1])}}return e},get:function(e){if(!this.root)return null;for(var t,a,r,i=this.xref,n=i.fetchIfRef(this.root),s=0;n.has("Kids");){if(++s>10){S('Search depth limit reached for "'+this._type+'" tree.');return null}var o=n.get("Kids");if(!b(o))return null;t=0;a=o.length-1;for(;t<=a;){r=t+a>>1;var c=i.fetchIfRef(o[r]),l=c.get("Limits");if(e<i.fetchIfRef(l[0]))a=r-1;else{if(!(e>i.fetchIfRef(l[1]))){n=i.fetchIfRef(o[r]);break}t=r+1}}if(t>a)return null}var h=n.get(this._type);if(b(h)){t=0;a=h.length-2;for(;t<=a;){r=t+a&-2;var u=i.fetchIfRef(h[r]);if(e<u)a=r-2;else{if(!(e>u))return i.fetchIfRef(h[r+1]);t=r+2}}}return null}};return e}(),X=function(){function e(e,t){this.root=e;this.xref=t;this._type="Names"}I.inherit(e,G,{});return e}(),V=function(){function e(e,t){this.root=e;this.xref=t;this._type="Nums"}I.inherit(e,G,{});return e}(),W=function(){function e(e,t){if(e&&E(e)){this.xref=t;this.root=e;e.has("FS")&&(this.fs=e.get("FS"));this.description=e.has("Desc")?C(e.get("Desc")):"";e.has("RF")&&S("Related file specifications are not supported");this.contentAvailable=!0;if(!e.has("EF")){this.contentAvailable=!1;S("Non-embedded file specifications are not supported")}}}function t(e){return e.has("UF")?e.get("UF"):e.has("F")?e.get("F"):e.has("Unix")?e.get("Unix"):e.has("Mac")?e.get("Mac"):e.has("DOS")?e.get("DOS"):null}e.prototype={get filename(){if(!this._filename&&this.root){var e=t(this.root)||"unnamed";this._filename=C(e).replace(/\\\\/g,"\\").replace(/\\\//g,"/").replace(/\\/g,"/")}return this._filename},get content(){if(!this.contentAvailable)return null;!this.contentRef&&this.root&&(this.contentRef=t(this.root.get("EF")));var e=null;if(this.contentRef){var a=this.xref,r=a.fetchIfRef(this.contentRef);r&&F(r)?e=r.getBytes():S("Embedded file specification points to non-existing/invalid content")}else S("Embedded file specification does not have a content");return e},get serializable(){return{filename:this.filename,content:this.content}}};return e}(),K=function(){function e(e){return L(e)||E(e)||b(e)||F(e)}function t(t,a){var r;if(E(t)||F(t)){var i;i=E(t)?t.map:t.dict.map;for(var n in i){r=i[n];e(r)&&a.push(r)}}else if(b(t))for(var s=0,o=t.length;s<o;s++){r=t[s];e(r)&&a.push(r)}}function a(e,t,a){this.obj=e;this.keys=t;this.xref=a;this.refSet=null;this.capability=null}a.prototype={load:function(){var e=this.keys;this.capability=g();if(!(this.xref.stream instanceof j)||0===this.xref.stream.getMissingChunks().length){this.capability.resolve();return this.capability.promise}this.refSet=new T;for(var t=[],a=0;a<e.length;a++)t.push(this.obj[e[a]]);this._walk(t);return this.capability.promise},_walk:function(e){for(var a=[],r=[];e.length;){var i=e.pop();if(L(i)){if(this.refSet.has(i))continue;try{var n=i;this.refSet.put(n);i=this.xref.fetch(i)}catch(e){if(!(e instanceof h))throw e;a.push(i);r.push({begin:e.begin,end:e.end})}}if(i&&i.getBaseStreams){for(var s=i.getBaseStreams(),o=!1,c=0;c<s.length;c++){var l=s[c];if(l.getMissingChunks&&l.getMissingChunks().length){o=!0;r.push({begin:l.start,end:l.end})}}o&&a.push(i)}t(i,e)}if(r.length)this.xref.stream.manager.requestRanges(r).then(function(){e=a;for(var t=0;t<a.length;t++){var r=a[t];L(r)&&this.refSet.remove(r)}this._walk(e)}.bind(this),this.capability.reject);else{this.refSet=null;this.capability.resolve()}}};return a}();t.Catalog=z;t.ObjectLoader=K;t.XRef=H;t.FileSpec=W},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.ArialNarrow="Helvetica";e["ArialNarrow-Bold"]="Helvetica-Bold";e["ArialNarrow-BoldItalic"]="Helvetica-BoldOblique";e["ArialNarrow-Italic"]="Helvetica-Oblique";e.ArialBlack="Helvetica";e["ArialBlack-Bold"]="Helvetica-Bold";e["ArialBlack-BoldItalic"]="Helvetica-BoldOblique";e["ArialBlack-Italic"]="Helvetica-Oblique";e["Arial-Black"]="Helvetica";e["Arial-Black-Bold"]="Helvetica-Bold";e["Arial-Black-BoldItalic"]="Helvetica-BoldOblique";e["Arial-Black-Italic"]="Helvetica-Oblique";e.Arial="Helvetica";e["Arial-Bold"]="Helvetica-Bold";e["Arial-BoldItalic"]="Helvetica-BoldOblique";e["Arial-Italic"]="Helvetica-Oblique";e["Arial-BoldItalicMT"]="Helvetica-BoldOblique";e["Arial-BoldMT"]="Helvetica-Bold";e["Arial-ItalicMT"]="Helvetica-Oblique";e.ArialMT="Helvetica";e["Courier-Bold"]="Courier-Bold";e["Courier-BoldItalic"]="Courier-BoldOblique";e["Courier-Italic"]="Courier-Oblique";e.CourierNew="Courier";e["CourierNew-Bold"]="Courier-Bold";e["CourierNew-BoldItalic"]="Courier-BoldOblique";e["CourierNew-Italic"]="Courier-Oblique";e["CourierNewPS-BoldItalicMT"]="Courier-BoldOblique";e["CourierNewPS-BoldMT"]="Courier-Bold";e["CourierNewPS-ItalicMT"]="Courier-Oblique";e.CourierNewPSMT="Courier";e.Helvetica="Helvetica";e["Helvetica-Bold"]="Helvetica-Bold";e["Helvetica-BoldItalic"]="Helvetica-BoldOblique";e["Helvetica-BoldOblique"]="Helvetica-BoldOblique";e["Helvetica-Italic"]="Helvetica-Oblique";e["Helvetica-Oblique"]="Helvetica-Oblique";e["Symbol-Bold"]="Symbol";e["Symbol-BoldItalic"]="Symbol";e["Symbol-Italic"]="Symbol";e.TimesNewRoman="Times-Roman";e["TimesNewRoman-Bold"]="Times-Bold";e["TimesNewRoman-BoldItalic"]="Times-BoldItalic";e["TimesNewRoman-Italic"]="Times-Italic";e.TimesNewRomanPS="Times-Roman";e["TimesNewRomanPS-Bold"]="Times-Bold";e["TimesNewRomanPS-BoldItalic"]="Times-BoldItalic";e["TimesNewRomanPS-BoldItalicMT"]="Times-BoldItalic";e["TimesNewRomanPS-BoldMT"]="Times-Bold";e["TimesNewRomanPS-Italic"]="Times-Italic";e["TimesNewRomanPS-ItalicMT"]="Times-Italic";e.TimesNewRomanPSMT="Times-Roman";e["TimesNewRomanPSMT-Bold"]="Times-Bold";e["TimesNewRomanPSMT-BoldItalic"]="Times-BoldItalic";e["TimesNewRomanPSMT-Italic"]="Times-Italic"}),s=i(function(e){e.CenturyGothic="Helvetica";e["CenturyGothic-Bold"]="Helvetica-Bold";e["CenturyGothic-BoldItalic"]="Helvetica-BoldOblique";e["CenturyGothic-Italic"]="Helvetica-Oblique";e.ComicSansMS="Comic Sans MS";e["ComicSansMS-Bold"]="Comic Sans MS-Bold";e["ComicSansMS-BoldItalic"]="Comic Sans MS-BoldItalic";e["ComicSansMS-Italic"]="Comic Sans MS-Italic";e.LucidaConsole="Courier";e["LucidaConsole-Bold"]="Courier-Bold";e["LucidaConsole-BoldItalic"]="Courier-BoldOblique";e["LucidaConsole-Italic"]="Courier-Oblique";e["MS-Gothic"]="MS Gothic";e["MS-Gothic-Bold"]="MS Gothic-Bold";e["MS-Gothic-BoldItalic"]="MS Gothic-BoldItalic";e["MS-Gothic-Italic"]="MS Gothic-Italic";e["MS-Mincho"]="MS Mincho";e["MS-Mincho-Bold"]="MS Mincho-Bold";e["MS-Mincho-BoldItalic"]="MS Mincho-BoldItalic";e["MS-Mincho-Italic"]="MS Mincho-Italic";e["MS-PGothic"]="MS PGothic";e["MS-PGothic-Bold"]="MS PGothic-Bold";e["MS-PGothic-BoldItalic"]="MS PGothic-BoldItalic";e["MS-PGothic-Italic"]="MS PGothic-Italic";e["MS-PMincho"]="MS PMincho";e["MS-PMincho-Bold"]="MS PMincho-Bold";e["MS-PMincho-BoldItalic"]="MS PMincho-BoldItalic";e["MS-PMincho-Italic"]="MS PMincho-Italic";e.NuptialScript="Times-Italic";e.Wingdings="ZapfDingbats"}),o=i(function(e){e["Adobe Jenson"]=!0;e["Adobe Text"]=!0;e.Albertus=!0;e.Aldus=!0;e.Alexandria=!0;e.Algerian=!0;e["American Typewriter"]=!0;e.Antiqua=!0;e.Apex=!0;e.Arno=!0;e.Aster=!0;e.Aurora=!0;e.Baskerville=!0;e.Bell=!0;e.Bembo=!0;e["Bembo Schoolbook"]=!0;e.Benguiat=!0;e["Berkeley Old Style"]=!0;e["Bernhard Modern"]=!0;e["Berthold City"]=!0;e.Bodoni=!0;e["Bauer Bodoni"]=!0;e["Book Antiqua"]=!0;e.Bookman=!0;e["Bordeaux Roman"]=!0;e["Californian FB"]=!0;e.Calisto=!0;e.Calvert=!0;e.Capitals=!0;e.Cambria=!0;e.Cartier=!0;e.Caslon=!0;e.Catull=!0;e.Centaur=!0;e["Century Old Style"]=!0;e["Century Schoolbook"]=!0;e.Chaparral=!0;e["Charis SIL"]=!0;e.Cheltenham=!0;e["Cholla Slab"]=!0;e.Clarendon=!0;e.Clearface=!0;e.Cochin=!0;e.Colonna=!0;e["Computer Modern"]=!0;e["Concrete Roman"]=!0;e.Constantia=!0;e["Cooper Black"]=!0;e.Corona=!0;e.Ecotype=!0;e.Egyptienne=!0;e.Elephant=!0;e.Excelsior=!0;e.Fairfield=!0;e["FF Scala"]=!0;e.Folkard=!0;e.Footlight=!0;e.FreeSerif=!0;e["Friz Quadrata"]=!0;e.Garamond=!0;e.Gentium=!0;e.Georgia=!0;e.Gloucester=!0;e["Goudy Old Style"]=!0;e["Goudy Schoolbook"]=!0;e["Goudy Pro Font"]=!0;e.Granjon=!0;e["Guardian Egyptian"]=!0;e.Heather=!0;e.Hercules=!0;e["High Tower Text"]=!0;e.Hiroshige=!0;e["Hoefler Text"]=!0;e["Humana Serif"]=!0;e.Imprint=!0;e["Ionic No. 5"]=!0;e.Janson=!0;e.Joanna=!0;e.Korinna=!0;e.Lexicon=!0;e["Liberation Serif"]=!0;e["Linux Libertine"]=!0;e.Literaturnaya=!0;e.Lucida=!0;e["Lucida Bright"]=!0;e.Melior=!0;e.Memphis=!0;e.Miller=!0;e.Minion=!0;e.Modern=!0;e["Mona Lisa"]=!0;e["Mrs Eaves"]=!0;e["MS Serif"]=!0;e["Museo Slab"]=!0;e["New York"]=!0;e["Nimbus Roman"]=!0;e["NPS Rawlinson Roadway"]=!0;e.NuptialScript=!0;e.Palatino=!0;e.Perpetua=!0;e.Plantin=!0;e["Plantin Schoolbook"]=!0;e.Playbill=!0;e["Poor Richard"]=!0;e["Rawlinson Roadway"]=!0;e.Renault=!0;e.Requiem=!0;e.Rockwell=!0;e.Roman=!0;e["Rotis Serif"]=!0;e.Sabon=!0;e.Scala=!0;e.Seagull=!0;e.Sistina=!0;e.Souvenir=!0;e.STIX=!0;e["Stone Informal"]=!0;e["Stone Serif"]=!0;e.Sylfaen=!0;e.Times=!0;e.Trajan=!0;e["Trinité"]=!0;e["Trump Mediaeval"]=!0;e.Utopia=!0;e["Vale Type"]=!0;e["Bitstream Vera"]=!0;e["Vera Serif"]=!0;e.Versailles=!0;e.Wanted=!0;e.Weiss=!0;e["Wide Latin"]=!0;e.Windsor=!0;e.XITS=!0}),c=i(function(e){e.Dingbats=!0;e.Symbol=!0;e.ZapfDingbats=!0}),l=i(function(e){e[2]=10;e[3]=32;e[4]=33;e[5]=34;e[6]=35;e[7]=36;e[8]=37;e[9]=38;e[10]=39;e[11]=40;e[12]=41;e[13]=42;e[14]=43;e[15]=44;e[16]=45;e[17]=46;e[18]=47;e[19]=48;e[20]=49;e[21]=50;e[22]=51;e[23]=52;e[24]=53;e[25]=54;e[26]=55;e[27]=56;e[28]=57;e[29]=58;e[30]=894;e[31]=60;e[32]=61;e[33]=62;e[34]=63;e[35]=64;e[36]=65;e[37]=66;e[38]=67;e[39]=68;e[40]=69;e[41]=70;e[42]=71;e[43]=72;e[44]=73;e[45]=74;e[46]=75;e[47]=76;e[48]=77;e[49]=78;e[50]=79;e[51]=80;e[52]=81;e[53]=82;e[54]=83;e[55]=84;e[56]=85;e[57]=86;e[58]=87;e[59]=88;e[60]=89;e[61]=90;e[62]=91;e[63]=92;e[64]=93;e[65]=94;e[66]=95;e[67]=96;e[68]=97;e[69]=98;e[70]=99;e[71]=100;e[72]=101;e[73]=102;e[74]=103;e[75]=104;e[76]=105;e[77]=106;e[78]=107;e[79]=108;e[80]=109;e[81]=110;e[82]=111;e[83]=112;e[84]=113;e[85]=114;e[86]=115;e[87]=116;e[88]=117;e[89]=118;e[90]=119;e[91]=120;e[92]=121;e[93]=122;e[94]=123;e[95]=124;e[96]=125;e[97]=126;e[98]=196;e[99]=197;e[100]=199;e[101]=201;e[102]=209;e[103]=214;e[104]=220;e[105]=225;e[106]=224;e[107]=226;e[108]=228;e[109]=227;e[110]=229;e[111]=231;e[112]=233;e[113]=232;e[114]=234;e[115]=235;e[116]=237;e[117]=236;e[118]=238;e[119]=239;e[120]=241;e[121]=243;e[122]=242;e[123]=244;e[124]=246;e[125]=245;e[126]=250;e[127]=249;e[128]=251;e[129]=252;e[130]=8224;e[131]=176;e[132]=162;e[133]=163;e[134]=167;e[135]=8226;e[136]=182;e[137]=223;e[138]=174;e[139]=169;e[140]=8482;e[141]=180;e[142]=168;e[143]=8800;e[144]=198;e[145]=216;e[146]=8734;e[147]=177;e[148]=8804;e[149]=8805;e[150]=165;e[151]=181;e[152]=8706;e[153]=8721;e[154]=8719;e[156]=8747;e[157]=170;e[158]=186;e[159]=8486;e[160]=230;e[161]=248;e[162]=191;e[163]=161;e[164]=172;e[165]=8730;e[166]=402;e[167]=8776;e[168]=8710;e[169]=171;e[170]=187;e[171]=8230;e[210]=218;e[223]=711;e[224]=321;e[225]=322;e[227]=353;e[229]=382;e[234]=253;e[252]=263;e[253]=268;e[254]=269;e[258]=258;e[260]=260;e[261]=261;e[265]=280;e[266]=281;e[268]=283;e[269]=313;e[275]=323;e[276]=324;e[278]=328;e[284]=345;e[285]=346;e[286]=347;e[292]=367;e[295]=377;e[296]=378;e[298]=380;e[305]=963;e[306]=964;e[307]=966;e[308]=8215;e[309]=8252;e[310]=8319;e[311]=8359;e[312]=8592;e[313]=8593;e[337]=9552;e[493]=1039;e[494]=1040;e[705]=1524;e[706]=8362;e[710]=64288;e[711]=64298;e[759]=1617;e[761]=1776;e[763]=1778;e[775]=1652;e[777]=1764;e[778]=1780;e[779]=1781;e[780]=1782;e[782]=771;e[783]=64726;e[786]=8363;e[788]=8532;e[790]=768;e[791]=769;e[792]=768;e[795]=803;e[797]=64336;e[798]=64337;e[799]=64342;e[800]=64343;e[801]=64344;e[802]=64345;e[803]=64362;e[804]=64363;e[805]=64364;e[2424]=7821;e[2425]=7822;e[2426]=7823;e[2427]=7824;e[2428]=7825;e[2429]=7826;e[2430]=7827;e[2433]=7682;e[2678]=8045;e[2679]=8046;e[2830]=1552;e[2838]=686;e[2840]=751;e[2842]=753;e[2843]=754;e[2844]=755;e[2846]=757;e[2856]=767;e[2857]=848;e[2858]=849;e[2862]=853;e[2863]=854;e[2864]=855;e[2865]=861;e[2866]=862;e[2906]=7460;e[2908]=7462;e[2909]=7463;e[2910]=7464;e[2912]=7466;e[2913]=7467;e[2914]=7468;e[2916]=7470;e[2917]=7471;e[2918]=7472;e[2920]=7474;e[2921]=7475;e[2922]=7476;e[2924]=7478;e[2925]=7479;e[2926]=7480;e[2928]=7482;e[2929]=7483;e[2930]=7484;e[2932]=7486;e[2933]=7487;e[2934]=7488;e[2936]=7490;e[2937]=7491;e[2938]=7492;e[2940]=7494;e[2941]=7495;e[2942]=7496;e[2944]=7498;e[2946]=7500;e[2948]=7502;e[2950]=7504;e[2951]=7505;e[2952]=7506;e[2954]=7508;e[2955]=7509;e[2956]=7510;e[2958]=7512;e[2959]=7513;e[2960]=7514;e[2962]=7516;e[2963]=7517;e[2964]=7518;e[2966]=7520;e[2967]=7521;e[2968]=7522;e[2970]=7524;e[2971]=7525;e[2972]=7526;e[2974]=7528;e[2975]=7529;e[2976]=7530;e[2978]=1537;e[2979]=1538;e[2980]=1539;e[2982]=1549;e[2983]=1551;e[2984]=1552;e[2986]=1554;e[2987]=1555;e[2988]=1556;e[2990]=1623;e[2991]=1624;e[2995]=1775;e[2999]=1791;e[3002]=64290;e[3003]=64291;e[3004]=64292;e[3006]=64294;e[3007]=64295;e[3008]=64296;e[3011]=1900;e[3014]=8223;e[3015]=8244;e[3017]=7532;e[3018]=7533;e[3019]=7534;e[3075]=7590;e[3076]=7591;e[3079]=7594;e[3080]=7595;e[3083]=7598;e[3084]=7599;e[3087]=7602;e[3088]=7603;e[3091]=7606;e[3092]=7607;e[3095]=7610;e[3096]=7611;e[3099]=7614;e[3100]=7615;e[3103]=7618;e[3104]=7619;e[3107]=8337;e[3108]=8338;e[3116]=1884;e[3119]=1885;e[3120]=1885;e[3123]=1886;e[3124]=1886;e[3127]=1887;e[3128]=1887;e[3131]=1888;e[3132]=1888;e[3135]=1889;e[3136]=1889;e[3139]=1890;e[3140]=1890;e[3143]=1891;e[3144]=1891;e[3147]=1892;e[3148]=1892;e[3153]=580;e[3154]=581;e[3157]=584;e[3158]=585;e[3161]=588;e[3162]=589;e[3165]=891;e[3166]=892;e[3169]=1274;e[3170]=1275;e[3173]=1278;e[3174]=1279;e[3181]=7622;e[3182]=7623;e[3282]=11799;e[3316]=578;e[3379]=42785;e[3393]=1159;e[3416]=8377}),h=i(function(e){e[227]=322;e[264]=261;e[291]=346});t.getStdFontMap=n;t.getNonStdFontMap=s;t.getSerifFonts=o;t.getSymbolsFonts=c;t.getGlyphMapForStandardFonts=l;t.getSupplementalGlyphMapForArialBlack=h},function(e,t,a){"use strict";function r(e){return e>=65520&&e<=65535?0:e>=62976&&e<=63743?h()[e]||e:e}function i(e,t){var a=t[e];if(void 0!==a)return a;if(!e)return-1;if("u"===e[0]){var r,i=e.length;if(7===i&&"n"===e[1]&&"i"===e[2])r=e.substr(3);else{if(!(i>=5&&i<=7))return-1;r=e.substr(1)}if(r===r.toUpperCase()){a=parseInt(r,16);if(a>=0)return a}}return-1}function n(e){for(var t=0,a=u.length;t<a;t++){var r=u[t];if(e>=r.begin&&e<r.end)return t}return-1}function s(e){var t=u[13];if(e>=t.begin&&e<t.end)return!0;t=u[11];return e>=t.begin&&e<t.end}function o(e){var t=e.length;if(t<=1||!s(e.charCodeAt(0)))return e;for(var a="",r=t-1;r>=0;r--)a+=e[r];return a}var c=a(0),l=c.getLookupTableFactory,h=l(function(e){e[63721]=169;e[63193]=169;e[63720]=174;e[63194]=174;e[63722]=8482;e[63195]=8482;e[63729]=9127;e[63730]=9128;e[63731]=9129;e[63740]=9131;e[63741]=9132;e[63742]=9133;e[63726]=9121;e[63727]=9122;e[63728]=9123;e[63737]=9124;e[63738]=9125;e[63739]=9126;e[63723]=9115;e[63724]=9116;e[63725]=9117;e[63734]=9118;e[63735]=9119;e[63736]=9120}),u=[{begin:0,end:127},{begin:128,end:255},{begin:256,end:383},{begin:384,end:591},{begin:592,end:687},{begin:688,end:767},{begin:768,end:879},{begin:880,end:1023},{begin:11392,end:11519},{begin:1024,end:1279},{begin:1328,end:1423},{begin:1424,end:1535},{begin:42240,end:42559},{begin:1536,end:1791},{begin:1984,end:2047},{begin:2304,end:2431},{begin:2432,end:2559},{begin:2560,end:2687},{begin:2688,end:2815},{begin:2816,end:2943},{begin:2944,end:3071},{begin:3072,end:3199},{begin:3200,end:3327},{begin:3328,end:3455},{begin:3584,end:3711},{begin:3712,end:3839},{begin:4256,end:4351},{begin:6912,end:7039},{begin:4352,end:4607},{begin:7680,end:7935},{begin:7936,end:8191},{begin:8192,end:8303},{begin:8304,end:8351},{begin:8352,end:8399},{begin:8400,end:8447},{begin:8448,end:8527},{begin:8528,end:8591},{begin:8592,end:8703},{begin:8704,end:8959},{begin:8960,end:9215},{begin:9216,end:9279},{begin:9280,end:9311},{begin:9312,end:9471},{begin:9472,end:9599},{begin:9600,end:9631},{begin:9632,end:9727},{begin:9728,end:9983},{begin:9984,end:10175},{begin:12288,end:12351},{begin:12352,end:12447},{begin:12448,end:12543},{begin:12544,end:12591},{begin:12592,end:12687},{begin:43072,end:43135},{begin:12800,end:13055},{begin:13056,end:13311},{begin:44032,end:55215},{begin:55296,end:57343},{begin:67840,end:67871},{begin:19968,end:40959},{begin:57344,end:63743},{begin:12736,end:12783},{begin:64256,end:64335},{begin:64336,end:65023},{begin:65056,end:65071},{begin:65040,end:65055},{begin:65104,end:65135},{begin:65136,end:65279},{begin:65280,end:65519},{begin:65520,end:65535},{begin:3840,end:4095},{begin:1792,end:1871},{begin:1920,end:1983},{begin:3456,end:3583},{begin:4096,end:4255},{begin:4608,end:4991},{begin:5024,end:5119},{begin:5120,end:5759},{begin:5760,end:5791},{begin:5792,end:5887},{begin:6016,end:6143},{begin:6144,end:6319},{begin:10240,end:10495},{begin:40960,end:42127},{begin:5888,end:5919},{begin:66304,end:66351},{begin:66352,end:66383},{begin:66560,end:66639},{begin:118784,end:119039},{begin:119808,end:120831},{begin:1044480,end:1048573},{begin:65024,end:65039},{begin:917504,end:917631},{begin:6400,end:6479},{begin:6480,end:6527},{begin:6528,end:6623},{begin:6656,end:6687},{begin:11264,end:11359},{begin:11568,end:11647},{begin:19904,end:19967},{begin:43008,end:43055},{begin:65536,end:65663},{begin:65856,end:65935},{begin:66432,end:66463},{begin:66464,end:66527},{begin:66640,end:66687},{begin:66688,end:66735},{begin:67584,end:67647},{begin:68096,end:68191},{begin:119552,end:119647},{begin:73728,end:74751},{begin:119648,end:119679},{begin:7040,end:7103},{begin:7168,end:7247},{begin:7248,end:7295},{begin:43136,end:43231},{begin:43264,end:43311},{begin:43312,end:43359},{begin:43520,end:43615},{begin:65936,end:65999},{begin:66e3,end:66047},{begin:66208,end:66271},{begin:127024,end:127135}],f=l(function(e){e["¨"]=" ̈";e["¯"]=" ̄";e["´"]=" ́";e["µ"]="μ";e["¸"]=" ̧";e["IJ"]="IJ";e["ij"]="ij";e["Ŀ"]="L·";e["ŀ"]="l·";e["ʼn"]="ʼn";e["ſ"]="s";e["DŽ"]="DŽ";e["Dž"]="Dž";e["dž"]="dž";e["LJ"]="LJ";e["Lj"]="Lj";e["lj"]="lj";e["NJ"]="NJ";e["Nj"]="Nj";e["nj"]="nj";e["DZ"]="DZ";e["Dz"]="Dz";e["dz"]="dz";e["˘"]=" ̆";e["˙"]=" ̇";e["˚"]=" ̊";e["˛"]=" ̨";e["˜"]=" ̃";e["˝"]=" ̋";e["ͺ"]=" ͅ";e["΄"]=" ́";e["ϐ"]="β";e["ϑ"]="θ";e["ϒ"]="Υ";e["ϕ"]="φ";e["ϖ"]="π";e["ϰ"]="κ";e["ϱ"]="ρ";e["ϲ"]="ς";e["ϴ"]="Θ";e["ϵ"]="ε";e["Ϲ"]="Σ";e["և"]="եւ";e["ٵ"]="اٴ";e["ٶ"]="وٴ";e["ٷ"]="ۇٴ";e["ٸ"]="يٴ";e["ำ"]="ํา";e["ຳ"]="ໍາ";e["ໜ"]="ຫນ";e["ໝ"]="ຫມ";e["ཷ"]="ྲཱྀ";e["ཹ"]="ླཱྀ";e["ẚ"]="aʾ";e["᾽"]=" ̓";e["᾿"]=" ̓";e["῀"]=" ͂";e["῾"]=" ̔";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e["‗"]=" ̳";e["․"]=".";e["‥"]="..";e["…"]="...";e["″"]="′′";e["‴"]="′′′";e["‶"]="‵‵";e["‷"]="‵‵‵";e["‼"]="!!";e["‾"]=" ̅";e["⁇"]="??";e["⁈"]="?!";e["⁉"]="!?";e["⁗"]="′′′′";e[" "]=" ";e["₨"]="Rs";e["℀"]="a/c";e["℁"]="a/s";e["℃"]="°C";e["℅"]="c/o";e["℆"]="c/u";e["ℇ"]="Ɛ";e["℉"]="°F";e["№"]="No";e["℡"]="TEL";e["ℵ"]="א";e["ℶ"]="ב";e["ℷ"]="ג";e["ℸ"]="ד";e["℻"]="FAX";e["Ⅰ"]="I";e["Ⅱ"]="II";e["Ⅲ"]="III";e["Ⅳ"]="IV";e["Ⅴ"]="V";e["Ⅵ"]="VI";e["Ⅶ"]="VII";e["Ⅷ"]="VIII";e["Ⅸ"]="IX";e["Ⅹ"]="X";e["Ⅺ"]="XI";e["Ⅻ"]="XII";e["Ⅼ"]="L";e["Ⅽ"]="C";e["Ⅾ"]="D";e["Ⅿ"]="M";e["ⅰ"]="i";e["ⅱ"]="ii";e["ⅲ"]="iii";e["ⅳ"]="iv";e["ⅴ"]="v";e["ⅵ"]="vi";e["ⅶ"]="vii";e["ⅷ"]="viii";e["ⅸ"]="ix";e["ⅹ"]="x";e["ⅺ"]="xi";e["ⅻ"]="xii";e["ⅼ"]="l";e["ⅽ"]="c";e["ⅾ"]="d";e["ⅿ"]="m";e["∬"]="∫∫";e["∭"]="∫∫∫";e["∯"]="∮∮";e["∰"]="∮∮∮";e["⑴"]="(1)";e["⑵"]="(2)";e["⑶"]="(3)";e["⑷"]="(4)";e["⑸"]="(5)";e["⑹"]="(6)";e["⑺"]="(7)";e["⑻"]="(8)";e["⑼"]="(9)";e["⑽"]="(10)";e["⑾"]="(11)";e["⑿"]="(12)";e["⒀"]="(13)";e["⒁"]="(14)";e["⒂"]="(15)";e["⒃"]="(16)";e["⒄"]="(17)";e["⒅"]="(18)";e["⒆"]="(19)";e["⒇"]="(20)";e["⒈"]="1.";e["⒉"]="2.";e["⒊"]="3.";e["⒋"]="4.";e["⒌"]="5.";e["⒍"]="6.";e["⒎"]="7.";e["⒏"]="8.";e["⒐"]="9.";e["⒑"]="10.";e["⒒"]="11.";e["⒓"]="12.";e["⒔"]="13.";e["⒕"]="14.";e["⒖"]="15.";e["⒗"]="16.";e["⒘"]="17.";e["⒙"]="18.";e["⒚"]="19.";e["⒛"]="20.";e["⒜"]="(a)";e["⒝"]="(b)";e["⒞"]="(c)";e["⒟"]="(d)";e["⒠"]="(e)";e["⒡"]="(f)";e["⒢"]="(g)";e["⒣"]="(h)";e["⒤"]="(i)";e["⒥"]="(j)";e["⒦"]="(k)";e["⒧"]="(l)";e["⒨"]="(m)";e["⒩"]="(n)";e["⒪"]="(o)";e["⒫"]="(p)";e["⒬"]="(q)";e["⒭"]="(r)";e["⒮"]="(s)";e["⒯"]="(t)";e["⒰"]="(u)";e["⒱"]="(v)";e["⒲"]="(w)";e["⒳"]="(x)";e["⒴"]="(y)";e["⒵"]="(z)";e["⨌"]="∫∫∫∫";e["⩴"]="::=";e["⩵"]="==";e["⩶"]="===";e["⺟"]="母";e["⻳"]="龟";e["⼀"]="一";e["⼁"]="丨";e["⼂"]="丶";e["⼃"]="丿";e["⼄"]="乙";e["⼅"]="亅";e["⼆"]="二";e["⼇"]="亠";e["⼈"]="人";e["⼉"]="儿";e["⼊"]="入";e["⼋"]="八";e["⼌"]="冂";e["⼍"]="冖";e["⼎"]="冫";e["⼏"]="几";e["⼐"]="凵";e["⼑"]="刀";e["⼒"]="力";e["⼓"]="勹";e["⼔"]="匕";e["⼕"]="匚";e["⼖"]="匸";e["⼗"]="十";e["⼘"]="卜";e["⼙"]="卩";e["⼚"]="厂";e["⼛"]="厶";e["⼜"]="又";e["⼝"]="口";e["⼞"]="囗";e["⼟"]="土";e["⼠"]="士";e["⼡"]="夂";e["⼢"]="夊";e["⼣"]="夕";e["⼤"]="大";e["⼥"]="女";e["⼦"]="子";e["⼧"]="宀";e["⼨"]="寸";e["⼩"]="小";e["⼪"]="尢";e["⼫"]="尸";e["⼬"]="屮";e["⼭"]="山";e["⼮"]="巛";e["⼯"]="工";e["⼰"]="己";e["⼱"]="巾";e["⼲"]="干";e["⼳"]="幺";e["⼴"]="广";e["⼵"]="廴";e["⼶"]="廾";e["⼷"]="弋";e["⼸"]="弓";e["⼹"]="彐";e["⼺"]="彡";e["⼻"]="彳";e["⼼"]="心";e["⼽"]="戈";e["⼾"]="戶";e["⼿"]="手";e["⽀"]="支";e["⽁"]="攴";e["⽂"]="文";e["⽃"]="斗";e["⽄"]="斤";e["⽅"]="方";e["⽆"]="无";e["⽇"]="日";e["⽈"]="曰";e["⽉"]="月";e["⽊"]="木";e["⽋"]="欠";e["⽌"]="止";e["⽍"]="歹";e["⽎"]="殳";e["⽏"]="毋";e["⽐"]="比";e["⽑"]="毛";e["⽒"]="氏";e["⽓"]="气";e["⽔"]="水";e["⽕"]="火";e["⽖"]="爪";e["⽗"]="父";e["⽘"]="爻";e["⽙"]="爿";e["⽚"]="片";e["⽛"]="牙";e["⽜"]="牛";e["⽝"]="犬";e["⽞"]="玄";e["⽟"]="玉";e["⽠"]="瓜";e["⽡"]="瓦";e["⽢"]="甘";e["⽣"]="生";e["⽤"]="用";e["⽥"]="田";e["⽦"]="疋";e["⽧"]="疒";e["⽨"]="癶";e["⽩"]="白";e["⽪"]="皮";e["⽫"]="皿";e["⽬"]="目";e["⽭"]="矛";e["⽮"]="矢";e["⽯"]="石";e["⽰"]="示";e["⽱"]="禸";e["⽲"]="禾";e["⽳"]="穴";e["⽴"]="立";e["⽵"]="竹";e["⽶"]="米";e["⽷"]="糸";e["⽸"]="缶";e["⽹"]="网";e["⽺"]="羊";e["⽻"]="羽";e["⽼"]="老";e["⽽"]="而";e["⽾"]="耒";e["⽿"]="耳";e["⾀"]="聿";e["⾁"]="肉";e["⾂"]="臣";e["⾃"]="自";e["⾄"]="至";e["⾅"]="臼";e["⾆"]="舌";e["⾇"]="舛";e["⾈"]="舟";e["⾉"]="艮";e["⾊"]="色";e["⾋"]="艸";e["⾌"]="虍";e["⾍"]="虫";e["⾎"]="血";e["⾏"]="行";e["⾐"]="衣";e["⾑"]="襾";e["⾒"]="見";e["⾓"]="角";e["⾔"]="言";e["⾕"]="谷";e["⾖"]="豆";e["⾗"]="豕";e["⾘"]="豸";e["⾙"]="貝";e["⾚"]="赤";e["⾛"]="走";e["⾜"]="足";e["⾝"]="身";e["⾞"]="車";e["⾟"]="辛";e["⾠"]="辰";e["⾡"]="辵";e["⾢"]="邑";e["⾣"]="酉";e["⾤"]="釆";e["⾥"]="里";e["⾦"]="金";e["⾧"]="長";e["⾨"]="門";e["⾩"]="阜";e["⾪"]="隶";e["⾫"]="隹";e["⾬"]="雨";e["⾭"]="靑";e["⾮"]="非";e["⾯"]="面";e["⾰"]="革";e["⾱"]="韋";e["⾲"]="韭";e["⾳"]="音";e["⾴"]="頁";e["⾵"]="風";e["⾶"]="飛";e["⾷"]="食";e["⾸"]="首";e["⾹"]="香";e["⾺"]="馬";e["⾻"]="骨";e["⾼"]="高";e["⾽"]="髟";e["⾾"]="鬥";e["⾿"]="鬯";e["⿀"]="鬲";e["⿁"]="鬼";e["⿂"]="魚";e["⿃"]="鳥";e["⿄"]="鹵";e["⿅"]="鹿";e["⿆"]="麥";e["⿇"]="麻";e["⿈"]="黃";e["⿉"]="黍";e["⿊"]="黑";e["⿋"]="黹";e["⿌"]="黽";e["⿍"]="鼎";e["⿎"]="鼓";e["⿏"]="鼠";e["⿐"]="鼻";e["⿑"]="齊";e["⿒"]="齒";e["⿓"]="龍";e["⿔"]="龜";e["⿕"]="龠";e["〶"]="〒";e["〸"]="十";e["〹"]="卄";e["〺"]="卅";e["゛"]=" ゙";e["゜"]=" ゚";e["ㄱ"]="ᄀ";e["ㄲ"]="ᄁ";e["ㄳ"]="ᆪ";e["ㄴ"]="ᄂ";e["ㄵ"]="ᆬ";e["ㄶ"]="ᆭ";e["ㄷ"]="ᄃ";e["ㄸ"]="ᄄ";e["ㄹ"]="ᄅ";e["ㄺ"]="ᆰ";e["ㄻ"]="ᆱ";e["ㄼ"]="ᆲ";e["ㄽ"]="ᆳ";e["ㄾ"]="ᆴ";e["ㄿ"]="ᆵ";e["ㅀ"]="ᄚ";e["ㅁ"]="ᄆ";e["ㅂ"]="ᄇ";e["ㅃ"]="ᄈ";e["ㅄ"]="ᄡ";e["ㅅ"]="ᄉ";e["ㅆ"]="ᄊ";e["ㅇ"]="ᄋ";e["ㅈ"]="ᄌ";e["ㅉ"]="ᄍ";e["ㅊ"]="ᄎ";e["ㅋ"]="ᄏ";e["ㅌ"]="ᄐ";e["ㅍ"]="ᄑ";e["ㅎ"]="ᄒ";e["ㅏ"]="ᅡ";e["ㅐ"]="ᅢ";e["ㅑ"]="ᅣ";e["ㅒ"]="ᅤ";e["ㅓ"]="ᅥ";e["ㅔ"]="ᅦ";e["ㅕ"]="ᅧ";e["ㅖ"]="ᅨ";e["ㅗ"]="ᅩ";e["ㅘ"]="ᅪ";e["ㅙ"]="ᅫ";e["ㅚ"]="ᅬ";e["ㅛ"]="ᅭ";e["ㅜ"]="ᅮ";e["ㅝ"]="ᅯ";e["ㅞ"]="ᅰ";e["ㅟ"]="ᅱ";e["ㅠ"]="ᅲ";e["ㅡ"]="ᅳ";e["ㅢ"]="ᅴ";e["ㅣ"]="ᅵ";e["ㅤ"]="ᅠ";e["ㅥ"]="ᄔ";e["ㅦ"]="ᄕ";e["ㅧ"]="ᇇ";e["ㅨ"]="ᇈ";e["ㅩ"]="ᇌ";e["ㅪ"]="ᇎ";e["ㅫ"]="ᇓ";e["ㅬ"]="ᇗ";e["ㅭ"]="ᇙ";e["ㅮ"]="ᄜ";e["ㅯ"]="ᇝ";e["ㅰ"]="ᇟ";e["ㅱ"]="ᄝ";e["ㅲ"]="ᄞ";e["ㅳ"]="ᄠ";e["ㅴ"]="ᄢ";e["ㅵ"]="ᄣ";e["ㅶ"]="ᄧ";e["ㅷ"]="ᄩ";e["ㅸ"]="ᄫ";e["ㅹ"]="ᄬ";e["ㅺ"]="ᄭ";e["ㅻ"]="ᄮ";e["ㅼ"]="ᄯ";e["ㅽ"]="ᄲ";e["ㅾ"]="ᄶ";e["ㅿ"]="ᅀ";e["ㆀ"]="ᅇ";e["ㆁ"]="ᅌ";e["ㆂ"]="ᇱ";e["ㆃ"]="ᇲ";e["ㆄ"]="ᅗ";e["ㆅ"]="ᅘ";e["ㆆ"]="ᅙ";e["ㆇ"]="ᆄ";e["ㆈ"]="ᆅ";e["ㆉ"]="ᆈ";e["ㆊ"]="ᆑ";e["ㆋ"]="ᆒ";e["ㆌ"]="ᆔ";e["ㆍ"]="ᆞ";e["ㆎ"]="ᆡ";e["㈀"]="(ᄀ)";e["㈁"]="(ᄂ)";e["㈂"]="(ᄃ)";e["㈃"]="(ᄅ)";e["㈄"]="(ᄆ)";e["㈅"]="(ᄇ)";e["㈆"]="(ᄉ)";e["㈇"]="(ᄋ)";e["㈈"]="(ᄌ)";e["㈉"]="(ᄎ)";e["㈊"]="(ᄏ)";e["㈋"]="(ᄐ)";e["㈌"]="(ᄑ)";e["㈍"]="(ᄒ)";e["㈎"]="(가)";e["㈏"]="(나)";e["㈐"]="(다)";e["㈑"]="(라)";e["㈒"]="(마)";e["㈓"]="(바)";e["㈔"]="(사)";e["㈕"]="(아)";e["㈖"]="(자)";e["㈗"]="(차)";e["㈘"]="(카)" +;e["㈙"]="(타)";e["㈚"]="(파)";e["㈛"]="(하)";e["㈜"]="(주)";e["㈝"]="(오전)";e["㈞"]="(오후)";e["㈠"]="(一)";e["㈡"]="(二)";e["㈢"]="(三)";e["㈣"]="(四)";e["㈤"]="(五)";e["㈥"]="(六)";e["㈦"]="(七)";e["㈧"]="(八)";e["㈨"]="(九)";e["㈩"]="(十)";e["㈪"]="(月)";e["㈫"]="(火)";e["㈬"]="(水)";e["㈭"]="(木)";e["㈮"]="(金)";e["㈯"]="(土)";e["㈰"]="(日)";e["㈱"]="(株)";e["㈲"]="(有)";e["㈳"]="(社)";e["㈴"]="(名)";e["㈵"]="(特)";e["㈶"]="(財)";e["㈷"]="(祝)";e["㈸"]="(労)";e["㈹"]="(代)";e["㈺"]="(呼)";e["㈻"]="(学)";e["㈼"]="(監)";e["㈽"]="(企)";e["㈾"]="(資)";e["㈿"]="(協)";e["㉀"]="(祭)";e["㉁"]="(休)";e["㉂"]="(自)";e["㉃"]="(至)";e["㋀"]="1月";e["㋁"]="2月";e["㋂"]="3月";e["㋃"]="4月";e["㋄"]="5月";e["㋅"]="6月";e["㋆"]="7月";e["㋇"]="8月";e["㋈"]="9月";e["㋉"]="10月";e["㋊"]="11月";e["㋋"]="12月";e["㍘"]="0点";e["㍙"]="1点";e["㍚"]="2点";e["㍛"]="3点";e["㍜"]="4点";e["㍝"]="5点";e["㍞"]="6点";e["㍟"]="7点";e["㍠"]="8点";e["㍡"]="9点";e["㍢"]="10点";e["㍣"]="11点";e["㍤"]="12点";e["㍥"]="13点";e["㍦"]="14点";e["㍧"]="15点";e["㍨"]="16点";e["㍩"]="17点";e["㍪"]="18点";e["㍫"]="19点";e["㍬"]="20点";e["㍭"]="21点";e["㍮"]="22点";e["㍯"]="23点";e["㍰"]="24点";e["㏠"]="1日";e["㏡"]="2日";e["㏢"]="3日";e["㏣"]="4日";e["㏤"]="5日";e["㏥"]="6日";e["㏦"]="7日";e["㏧"]="8日";e["㏨"]="9日";e["㏩"]="10日";e["㏪"]="11日";e["㏫"]="12日";e["㏬"]="13日";e["㏭"]="14日";e["㏮"]="15日";e["㏯"]="16日";e["㏰"]="17日";e["㏱"]="18日";e["㏲"]="19日";e["㏳"]="20日";e["㏴"]="21日";e["㏵"]="22日";e["㏶"]="23日";e["㏷"]="24日";e["㏸"]="25日";e["㏹"]="26日";e["㏺"]="27日";e["㏻"]="28日";e["㏼"]="29日";e["㏽"]="30日";e["㏾"]="31日";e["ff"]="ff";e["fi"]="fi";e["fl"]="fl";e["ffi"]="ffi";e["ffl"]="ffl";e["ſt"]="ſt";e["st"]="st";e["ﬓ"]="մն";e["ﬔ"]="մե";e["ﬕ"]="մի";e["ﬖ"]="վն";e["ﬗ"]="մխ";e["ﭏ"]="אל";e["ﭐ"]="ٱ";e["ﭑ"]="ٱ";e["ﭒ"]="ٻ";e["ﭓ"]="ٻ";e["ﭔ"]="ٻ";e["ﭕ"]="ٻ";e["ﭖ"]="پ";e["ﭗ"]="پ";e["ﭘ"]="پ";e["ﭙ"]="پ";e["ﭚ"]="ڀ";e["ﭛ"]="ڀ";e["ﭜ"]="ڀ";e["ﭝ"]="ڀ";e["ﭞ"]="ٺ";e["ﭟ"]="ٺ";e["ﭠ"]="ٺ";e["ﭡ"]="ٺ";e["ﭢ"]="ٿ";e["ﭣ"]="ٿ";e["ﭤ"]="ٿ";e["ﭥ"]="ٿ";e["ﭦ"]="ٹ";e["ﭧ"]="ٹ";e["ﭨ"]="ٹ";e["ﭩ"]="ٹ";e["ﭪ"]="ڤ";e["ﭫ"]="ڤ";e["ﭬ"]="ڤ";e["ﭭ"]="ڤ";e["ﭮ"]="ڦ";e["ﭯ"]="ڦ";e["ﭰ"]="ڦ";e["ﭱ"]="ڦ";e["ﭲ"]="ڄ";e["ﭳ"]="ڄ";e["ﭴ"]="ڄ";e["ﭵ"]="ڄ";e["ﭶ"]="ڃ";e["ﭷ"]="ڃ";e["ﭸ"]="ڃ";e["ﭹ"]="ڃ";e["ﭺ"]="چ";e["ﭻ"]="چ";e["ﭼ"]="چ";e["ﭽ"]="چ";e["ﭾ"]="ڇ";e["ﭿ"]="ڇ";e["ﮀ"]="ڇ";e["ﮁ"]="ڇ";e["ﮂ"]="ڍ";e["ﮃ"]="ڍ";e["ﮄ"]="ڌ";e["ﮅ"]="ڌ";e["ﮆ"]="ڎ";e["ﮇ"]="ڎ";e["ﮈ"]="ڈ";e["ﮉ"]="ڈ";e["ﮊ"]="ژ";e["ﮋ"]="ژ";e["ﮌ"]="ڑ";e["ﮍ"]="ڑ";e["ﮎ"]="ک";e["ﮏ"]="ک";e["ﮐ"]="ک";e["ﮑ"]="ک";e["ﮒ"]="گ";e["ﮓ"]="گ";e["ﮔ"]="گ";e["ﮕ"]="گ";e["ﮖ"]="ڳ";e["ﮗ"]="ڳ";e["ﮘ"]="ڳ";e["ﮙ"]="ڳ";e["ﮚ"]="ڱ";e["ﮛ"]="ڱ";e["ﮜ"]="ڱ";e["ﮝ"]="ڱ";e["ﮞ"]="ں";e["ﮟ"]="ں";e["ﮠ"]="ڻ";e["ﮡ"]="ڻ";e["ﮢ"]="ڻ";e["ﮣ"]="ڻ";e["ﮤ"]="ۀ";e["ﮥ"]="ۀ";e["ﮦ"]="ہ";e["ﮧ"]="ہ";e["ﮨ"]="ہ";e["ﮩ"]="ہ";e["ﮪ"]="ھ";e["ﮫ"]="ھ";e["ﮬ"]="ھ";e["ﮭ"]="ھ";e["ﮮ"]="ے";e["ﮯ"]="ے";e["ﮰ"]="ۓ";e["ﮱ"]="ۓ";e["ﯓ"]="ڭ";e["ﯔ"]="ڭ";e["ﯕ"]="ڭ";e["ﯖ"]="ڭ";e["ﯗ"]="ۇ";e["ﯘ"]="ۇ";e["ﯙ"]="ۆ";e["ﯚ"]="ۆ";e["ﯛ"]="ۈ";e["ﯜ"]="ۈ";e["ﯝ"]="ٷ";e["ﯞ"]="ۋ";e["ﯟ"]="ۋ";e["ﯠ"]="ۅ";e["ﯡ"]="ۅ";e["ﯢ"]="ۉ";e["ﯣ"]="ۉ";e["ﯤ"]="ې";e["ﯥ"]="ې";e["ﯦ"]="ې";e["ﯧ"]="ې";e["ﯨ"]="ى";e["ﯩ"]="ى";e["ﯪ"]="ئا";e["ﯫ"]="ئا";e["ﯬ"]="ئە";e["ﯭ"]="ئە";e["ﯮ"]="ئو";e["ﯯ"]="ئو";e["ﯰ"]="ئۇ";e["ﯱ"]="ئۇ";e["ﯲ"]="ئۆ";e["ﯳ"]="ئۆ";e["ﯴ"]="ئۈ";e["ﯵ"]="ئۈ";e["ﯶ"]="ئې";e["ﯷ"]="ئې";e["ﯸ"]="ئې";e["ﯹ"]="ئى";e["ﯺ"]="ئى";e["ﯻ"]="ئى";e["ﯼ"]="ی";e["ﯽ"]="ی";e["ﯾ"]="ی";e["ﯿ"]="ی";e["ﰀ"]="ئج";e["ﰁ"]="ئح";e["ﰂ"]="ئم";e["ﰃ"]="ئى";e["ﰄ"]="ئي";e["ﰅ"]="بج";e["ﰆ"]="بح";e["ﰇ"]="بخ";e["ﰈ"]="بم";e["ﰉ"]="بى";e["ﰊ"]="بي";e["ﰋ"]="تج";e["ﰌ"]="تح";e["ﰍ"]="تخ";e["ﰎ"]="تم";e["ﰏ"]="تى";e["ﰐ"]="تي";e["ﰑ"]="ثج";e["ﰒ"]="ثم";e["ﰓ"]="ثى";e["ﰔ"]="ثي";e["ﰕ"]="جح";e["ﰖ"]="جم";e["ﰗ"]="حج";e["ﰘ"]="حم";e["ﰙ"]="خج";e["ﰚ"]="خح";e["ﰛ"]="خم";e["ﰜ"]="سج";e["ﰝ"]="سح";e["ﰞ"]="سخ";e["ﰟ"]="سم";e["ﰠ"]="صح";e["ﰡ"]="صم";e["ﰢ"]="ضج";e["ﰣ"]="ضح";e["ﰤ"]="ضخ";e["ﰥ"]="ضم";e["ﰦ"]="طح";e["ﰧ"]="طم";e["ﰨ"]="ظم";e["ﰩ"]="عج";e["ﰪ"]="عم";e["ﰫ"]="غج";e["ﰬ"]="غم";e["ﰭ"]="فج";e["ﰮ"]="فح";e["ﰯ"]="فخ";e["ﰰ"]="فم";e["ﰱ"]="فى";e["ﰲ"]="في";e["ﰳ"]="قح";e["ﰴ"]="قم";e["ﰵ"]="قى";e["ﰶ"]="قي";e["ﰷ"]="كا";e["ﰸ"]="كج";e["ﰹ"]="كح";e["ﰺ"]="كخ";e["ﰻ"]="كل";e["ﰼ"]="كم";e["ﰽ"]="كى";e["ﰾ"]="كي";e["ﰿ"]="لج";e["ﱀ"]="لح";e["ﱁ"]="لخ";e["ﱂ"]="لم";e["ﱃ"]="لى";e["ﱄ"]="لي";e["ﱅ"]="مج";e["ﱆ"]="مح";e["ﱇ"]="مخ";e["ﱈ"]="مم";e["ﱉ"]="مى";e["ﱊ"]="مي";e["ﱋ"]="نج";e["ﱌ"]="نح";e["ﱍ"]="نخ";e["ﱎ"]="نم";e["ﱏ"]="نى";e["ﱐ"]="ني";e["ﱑ"]="هج";e["ﱒ"]="هم";e["ﱓ"]="هى";e["ﱔ"]="هي";e["ﱕ"]="يج";e["ﱖ"]="يح";e["ﱗ"]="يخ";e["ﱘ"]="يم";e["ﱙ"]="يى";e["ﱚ"]="يي";e["ﱛ"]="ذٰ";e["ﱜ"]="رٰ";e["ﱝ"]="ىٰ";e["ﱞ"]=" ٌّ";e["ﱟ"]=" ٍّ";e["ﱠ"]=" َّ";e["ﱡ"]=" ُّ";e["ﱢ"]=" ِّ";e["ﱣ"]=" ّٰ";e["ﱤ"]="ئر";e["ﱥ"]="ئز";e["ﱦ"]="ئم";e["ﱧ"]="ئن";e["ﱨ"]="ئى";e["ﱩ"]="ئي";e["ﱪ"]="بر";e["ﱫ"]="بز";e["ﱬ"]="بم";e["ﱭ"]="بن";e["ﱮ"]="بى";e["ﱯ"]="بي";e["ﱰ"]="تر";e["ﱱ"]="تز";e["ﱲ"]="تم";e["ﱳ"]="تن";e["ﱴ"]="تى";e["ﱵ"]="تي";e["ﱶ"]="ثر";e["ﱷ"]="ثز";e["ﱸ"]="ثم";e["ﱹ"]="ثن";e["ﱺ"]="ثى";e["ﱻ"]="ثي";e["ﱼ"]="فى";e["ﱽ"]="في";e["ﱾ"]="قى";e["ﱿ"]="قي";e["ﲀ"]="كا";e["ﲁ"]="كل";e["ﲂ"]="كم";e["ﲃ"]="كى";e["ﲄ"]="كي";e["ﲅ"]="لم";e["ﲆ"]="لى";e["ﲇ"]="لي";e["ﲈ"]="ما";e["ﲉ"]="مم";e["ﲊ"]="نر";e["ﲋ"]="نز";e["ﲌ"]="نم";e["ﲍ"]="نن";e["ﲎ"]="نى";e["ﲏ"]="ني";e["ﲐ"]="ىٰ";e["ﲑ"]="ير";e["ﲒ"]="يز";e["ﲓ"]="يم";e["ﲔ"]="ين";e["ﲕ"]="يى";e["ﲖ"]="يي";e["ﲗ"]="ئج";e["ﲘ"]="ئح";e["ﲙ"]="ئخ";e["ﲚ"]="ئم";e["ﲛ"]="ئه";e["ﲜ"]="بج";e["ﲝ"]="بح";e["ﲞ"]="بخ";e["ﲟ"]="بم";e["ﲠ"]="به";e["ﲡ"]="تج";e["ﲢ"]="تح";e["ﲣ"]="تخ";e["ﲤ"]="تم";e["ﲥ"]="ته";e["ﲦ"]="ثم";e["ﲧ"]="جح";e["ﲨ"]="جم";e["ﲩ"]="حج";e["ﲪ"]="حم";e["ﲫ"]="خج";e["ﲬ"]="خم";e["ﲭ"]="سج";e["ﲮ"]="سح";e["ﲯ"]="سخ";e["ﲰ"]="سم";e["ﲱ"]="صح";e["ﲲ"]="صخ";e["ﲳ"]="صم";e["ﲴ"]="ضج";e["ﲵ"]="ضح";e["ﲶ"]="ضخ";e["ﲷ"]="ضم";e["ﲸ"]="طح";e["ﲹ"]="ظم";e["ﲺ"]="عج";e["ﲻ"]="عم";e["ﲼ"]="غج";e["ﲽ"]="غم";e["ﲾ"]="فج";e["ﲿ"]="فح";e["ﳀ"]="فخ";e["ﳁ"]="فم";e["ﳂ"]="قح";e["ﳃ"]="قم";e["ﳄ"]="كج";e["ﳅ"]="كح";e["ﳆ"]="كخ";e["ﳇ"]="كل";e["ﳈ"]="كم";e["ﳉ"]="لج";e["ﳊ"]="لح";e["ﳋ"]="لخ";e["ﳌ"]="لم";e["ﳍ"]="له";e["ﳎ"]="مج";e["ﳏ"]="مح";e["ﳐ"]="مخ";e["ﳑ"]="مم";e["ﳒ"]="نج";e["ﳓ"]="نح";e["ﳔ"]="نخ";e["ﳕ"]="نم";e["ﳖ"]="نه";e["ﳗ"]="هج";e["ﳘ"]="هم";e["ﳙ"]="هٰ";e["ﳚ"]="يج";e["ﳛ"]="يح";e["ﳜ"]="يخ";e["ﳝ"]="يم";e["ﳞ"]="يه";e["ﳟ"]="ئم";e["ﳠ"]="ئه";e["ﳡ"]="بم";e["ﳢ"]="به";e["ﳣ"]="تم";e["ﳤ"]="ته";e["ﳥ"]="ثم";e["ﳦ"]="ثه";e["ﳧ"]="سم";e["ﳨ"]="سه";e["ﳩ"]="شم";e["ﳪ"]="شه";e["ﳫ"]="كل";e["ﳬ"]="كم";e["ﳭ"]="لم";e["ﳮ"]="نم";e["ﳯ"]="نه";e["ﳰ"]="يم";e["ﳱ"]="يه";e["ﳲ"]="ـَّ";e["ﳳ"]="ـُّ";e["ﳴ"]="ـِّ";e["ﳵ"]="طى";e["ﳶ"]="طي";e["ﳷ"]="عى";e["ﳸ"]="عي";e["ﳹ"]="غى";e["ﳺ"]="غي";e["ﳻ"]="سى";e["ﳼ"]="سي";e["ﳽ"]="شى";e["ﳾ"]="شي";e["ﳿ"]="حى";e["ﴀ"]="حي";e["ﴁ"]="جى";e["ﴂ"]="جي";e["ﴃ"]="خى";e["ﴄ"]="خي";e["ﴅ"]="صى";e["ﴆ"]="صي";e["ﴇ"]="ضى";e["ﴈ"]="ضي";e["ﴉ"]="شج";e["ﴊ"]="شح";e["ﴋ"]="شخ";e["ﴌ"]="شم";e["ﴍ"]="شر";e["ﴎ"]="سر";e["ﴏ"]="صر";e["ﴐ"]="ضر";e["ﴑ"]="طى";e["ﴒ"]="طي";e["ﴓ"]="عى";e["ﴔ"]="عي";e["ﴕ"]="غى";e["ﴖ"]="غي";e["ﴗ"]="سى";e["ﴘ"]="سي";e["ﴙ"]="شى";e["ﴚ"]="شي";e["ﴛ"]="حى";e["ﴜ"]="حي";e["ﴝ"]="جى";e["ﴞ"]="جي";e["ﴟ"]="خى";e["ﴠ"]="خي";e["ﴡ"]="صى";e["ﴢ"]="صي";e["ﴣ"]="ضى";e["ﴤ"]="ضي";e["ﴥ"]="شج";e["ﴦ"]="شح";e["ﴧ"]="شخ";e["ﴨ"]="شم";e["ﴩ"]="شر";e["ﴪ"]="سر";e["ﴫ"]="صر";e["ﴬ"]="ضر";e["ﴭ"]="شج";e["ﴮ"]="شح";e["ﴯ"]="شخ";e["ﴰ"]="شم";e["ﴱ"]="سه";e["ﴲ"]="شه";e["ﴳ"]="طم";e["ﴴ"]="سج";e["ﴵ"]="سح";e["ﴶ"]="سخ";e["ﴷ"]="شج";e["ﴸ"]="شح";e["ﴹ"]="شخ";e["ﴺ"]="طم";e["ﴻ"]="ظم";e["ﴼ"]="اً";e["ﴽ"]="اً";e["ﵐ"]="تجم";e["ﵑ"]="تحج";e["ﵒ"]="تحج";e["ﵓ"]="تحم";e["ﵔ"]="تخم";e["ﵕ"]="تمج";e["ﵖ"]="تمح";e["ﵗ"]="تمخ";e["ﵘ"]="جمح";e["ﵙ"]="جمح";e["ﵚ"]="حمي";e["ﵛ"]="حمى";e["ﵜ"]="سحج";e["ﵝ"]="سجح";e["ﵞ"]="سجى";e["ﵟ"]="سمح";e["ﵠ"]="سمح";e["ﵡ"]="سمج";e["ﵢ"]="سمم";e["ﵣ"]="سمم";e["ﵤ"]="صحح";e["ﵥ"]="صحح";e["ﵦ"]="صمم";e["ﵧ"]="شحم";e["ﵨ"]="شحم";e["ﵩ"]="شجي";e["ﵪ"]="شمخ";e["ﵫ"]="شمخ";e["ﵬ"]="شمم";e["ﵭ"]="شمم";e["ﵮ"]="ضحى";e["ﵯ"]="ضخم";e["ﵰ"]="ضخم";e["ﵱ"]="طمح";e["ﵲ"]="طمح";e["ﵳ"]="طمم";e["ﵴ"]="طمي";e["ﵵ"]="عجم";e["ﵶ"]="عمم";e["ﵷ"]="عمم";e["ﵸ"]="عمى";e["ﵹ"]="غمم";e["ﵺ"]="غمي";e["ﵻ"]="غمى";e["ﵼ"]="فخم";e["ﵽ"]="فخم";e["ﵾ"]="قمح";e["ﵿ"]="قمم";e["ﶀ"]="لحم";e["ﶁ"]="لحي";e["ﶂ"]="لحى";e["ﶃ"]="لجج";e["ﶄ"]="لجج";e["ﶅ"]="لخم";e["ﶆ"]="لخم";e["ﶇ"]="لمح";e["ﶈ"]="لمح";e["ﶉ"]="محج";e["ﶊ"]="محم";e["ﶋ"]="محي";e["ﶌ"]="مجح";e["ﶍ"]="مجم";e["ﶎ"]="مخج";e["ﶏ"]="مخم";e["ﶒ"]="مجخ";e["ﶓ"]="همج";e["ﶔ"]="همم";e["ﶕ"]="نحم";e["ﶖ"]="نحى";e["ﶗ"]="نجم";e["ﶘ"]="نجم";e["ﶙ"]="نجى";e["ﶚ"]="نمي";e["ﶛ"]="نمى";e["ﶜ"]="يمم";e["ﶝ"]="يمم";e["ﶞ"]="بخي";e["ﶟ"]="تجي";e["ﶠ"]="تجى";e["ﶡ"]="تخي";e["ﶢ"]="تخى";e["ﶣ"]="تمي";e["ﶤ"]="تمى";e["ﶥ"]="جمي";e["ﶦ"]="جحى";e["ﶧ"]="جمى";e["ﶨ"]="سخى";e["ﶩ"]="صحي";e["ﶪ"]="شحي";e["ﶫ"]="ضحي";e["ﶬ"]="لجي";e["ﶭ"]="لمي";e["ﶮ"]="يحي";e["ﶯ"]="يجي";e["ﶰ"]="يمي";e["ﶱ"]="ممي";e["ﶲ"]="قمي";e["ﶳ"]="نحي";e["ﶴ"]="قمح";e["ﶵ"]="لحم";e["ﶶ"]="عمي";e["ﶷ"]="كمي";e["ﶸ"]="نجح";e["ﶹ"]="مخي";e["ﶺ"]="لجم";e["ﶻ"]="كمم";e["ﶼ"]="لجم";e["ﶽ"]="نجح";e["ﶾ"]="جحي";e["ﶿ"]="حجي";e["ﷀ"]="مجي";e["ﷁ"]="فمي";e["ﷂ"]="بحي";e["ﷃ"]="كمم";e["ﷄ"]="عجم";e["ﷅ"]="صمم";e["ﷆ"]="سخي";e["ﷇ"]="نجي";e["﹉"]="‾";e["﹊"]="‾";e["﹋"]="‾";e["﹌"]="‾";e["﹍"]="_";e["﹎"]="_";e["﹏"]="_";e["ﺀ"]="ء";e["ﺁ"]="آ";e["ﺂ"]="آ";e["ﺃ"]="أ";e["ﺄ"]="أ";e["ﺅ"]="ؤ";e["ﺆ"]="ؤ";e["ﺇ"]="إ";e["ﺈ"]="إ";e["ﺉ"]="ئ";e["ﺊ"]="ئ";e["ﺋ"]="ئ";e["ﺌ"]="ئ";e["ﺍ"]="ا";e["ﺎ"]="ا";e["ﺏ"]="ب";e["ﺐ"]="ب";e["ﺑ"]="ب";e["ﺒ"]="ب";e["ﺓ"]="ة";e["ﺔ"]="ة";e["ﺕ"]="ت";e["ﺖ"]="ت";e["ﺗ"]="ت";e["ﺘ"]="ت";e["ﺙ"]="ث";e["ﺚ"]="ث";e["ﺛ"]="ث";e["ﺜ"]="ث";e["ﺝ"]="ج";e["ﺞ"]="ج";e["ﺟ"]="ج";e["ﺠ"]="ج";e["ﺡ"]="ح";e["ﺢ"]="ح";e["ﺣ"]="ح";e["ﺤ"]="ح";e["ﺥ"]="خ";e["ﺦ"]="خ";e["ﺧ"]="خ";e["ﺨ"]="خ";e["ﺩ"]="د";e["ﺪ"]="د";e["ﺫ"]="ذ";e["ﺬ"]="ذ";e["ﺭ"]="ر";e["ﺮ"]="ر";e["ﺯ"]="ز";e["ﺰ"]="ز";e["ﺱ"]="س";e["ﺲ"]="س";e["ﺳ"]="س";e["ﺴ"]="س";e["ﺵ"]="ش";e["ﺶ"]="ش";e["ﺷ"]="ش";e["ﺸ"]="ش";e["ﺹ"]="ص";e["ﺺ"]="ص";e["ﺻ"]="ص";e["ﺼ"]="ص";e["ﺽ"]="ض";e["ﺾ"]="ض";e["ﺿ"]="ض";e["ﻀ"]="ض";e["ﻁ"]="ط";e["ﻂ"]="ط";e["ﻃ"]="ط";e["ﻄ"]="ط";e["ﻅ"]="ظ";e["ﻆ"]="ظ";e["ﻇ"]="ظ";e["ﻈ"]="ظ";e["ﻉ"]="ع";e["ﻊ"]="ع";e["ﻋ"]="ع";e["ﻌ"]="ع";e["ﻍ"]="غ";e["ﻎ"]="غ";e["ﻏ"]="غ";e["ﻐ"]="غ";e["ﻑ"]="ف";e["ﻒ"]="ف";e["ﻓ"]="ف";e["ﻔ"]="ف";e["ﻕ"]="ق";e["ﻖ"]="ق";e["ﻗ"]="ق";e["ﻘ"]="ق";e["ﻙ"]="ك";e["ﻚ"]="ك";e["ﻛ"]="ك";e["ﻜ"]="ك";e["ﻝ"]="ل";e["ﻞ"]="ل";e["ﻟ"]="ل";e["ﻠ"]="ل";e["ﻡ"]="م";e["ﻢ"]="م";e["ﻣ"]="م";e["ﻤ"]="م";e["ﻥ"]="ن";e["ﻦ"]="ن";e["ﻧ"]="ن";e["ﻨ"]="ن";e["ﻩ"]="ه";e["ﻪ"]="ه";e["ﻫ"]="ه";e["ﻬ"]="ه";e["ﻭ"]="و";e["ﻮ"]="و";e["ﻯ"]="ى";e["ﻰ"]="ى";e["ﻱ"]="ي";e["ﻲ"]="ي";e["ﻳ"]="ي";e["ﻴ"]="ي";e["ﻵ"]="لآ";e["ﻶ"]="لآ";e["ﻷ"]="لأ";e["ﻸ"]="لأ";e["ﻹ"]="لإ";e["ﻺ"]="لإ";e["ﻻ"]="لا";e["ﻼ"]="لا"});t.mapSpecialUnicodeValues=r;t.reverseIfRtl=o;t.getUnicodeRangeFor=n;t.getNormalizedUnicodes=f;t.getUnicodeForGlyph=i},function(e,t,a){"use strict";function r(e,t){this.url=e;t=t||{};this.isHttp=/^https?:/i.test(e);this.httpHeaders=this.isHttp&&t.httpHeaders||{};this.withCredentials=t.withCredentials||!1;this.getXhr=t.getXhr||function(){return new XMLHttpRequest};this.currXhrId=0;this.pendingRequests=Object.create(null);this.loadedRequests=Object.create(null)}function i(e){var t=e.response;if("string"!=typeof t)return t;for(var a=t.length,r=new Uint8Array(a),i=0;i<a;i++)r[i]=255&t.charCodeAt(i);return r.buffer}function n(e){this._options=e;var t=e.source;this._manager=new r(t.url,{httpHeaders:t.httpHeaders,withCredentials:t.withCredentials});this._rangeChunkSize=t.rangeChunkSize;this._fullRequestReader=null;this._rangeRequestReaders=[]}function s(e,t){this._manager=e;var a=t.source,r={onHeadersReceived:this._onHeadersReceived.bind(this),onProgressiveData:a.disableStream?null:this._onProgressiveData.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)};this._url=a.url;this._fullRequestId=e.requestFull(r);this._headersReceivedCapability=d();this._disableRange=t.disableRange||!1;this._contentLength=a.length;this._rangeChunkSize=a.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!1;this._isRangeSupported=!1;this._cachedChunks=[];this._requests=[];this._done=!1;this._storedError=void 0;this.onProgress=null}function o(e,t,a){this._manager=e;var r={onDone:this._onDone.bind(this),onProgress:this._onProgress.bind(this)};this._requestId=e.requestRange(t,a,r);this._requests=[];this._queuedChunk=null;this._done=!1;this.onProgress=null;this.onClosed=null}var c=a(0),l=a(8),h=c.globalScope,u=function(){try{var e=new XMLHttpRequest;e.open("GET",h.location.href);e.responseType="moz-chunked-arraybuffer";return"moz-chunked-arraybuffer"===e.responseType}catch(e){return!1}}();r.prototype={requestRange:function(e,t,a){var r={begin:e,end:t};for(var i in a)r[i]=a[i];return this.request(r)},requestFull:function(e){return this.request(e)},request:function(e){var t=this.getXhr(),a=this.currXhrId++,r=this.pendingRequests[a]={xhr:t};t.open("GET",this.url);t.withCredentials=this.withCredentials;for(var i in this.httpHeaders){var n=this.httpHeaders[i];void 0!==n&&t.setRequestHeader(i,n)}if(this.isHttp&&"begin"in e&&"end"in e){var s=e.begin+"-"+(e.end-1);t.setRequestHeader("Range","bytes="+s);r.expectedStatus=206}else r.expectedStatus=200;if(u&&!!e.onProgressiveData){t.responseType="moz-chunked-arraybuffer";r.onProgressiveData=e.onProgressiveData;r.mozChunked=!0}else t.responseType="arraybuffer";e.onError&&(t.onerror=function(a){e.onError(t.status)});t.onreadystatechange=this.onStateChange.bind(this,a);t.onprogress=this.onProgress.bind(this,a);r.onHeadersReceived=e.onHeadersReceived;r.onDone=e.onDone;r.onError=e.onError;r.onProgress=e.onProgress;t.send(null);return a},onProgress:function(e,t){var a=this.pendingRequests[e];if(a){if(a.mozChunked){var r=i(a.xhr);a.onProgressiveData(r)}var n=a.onProgress;n&&n(t)}},onStateChange:function(e,t){var a=this.pendingRequests[e];if(a){var r=a.xhr;if(r.readyState>=2&&a.onHeadersReceived){a.onHeadersReceived();delete a.onHeadersReceived}if(4===r.readyState&&e in this.pendingRequests){delete this.pendingRequests[e];if(0===r.status&&this.isHttp)a.onError&&a.onError(r.status);else{var n=r.status||200;if(200===n&&206===a.expectedStatus||n===a.expectedStatus){this.loadedRequests[e]=!0;var s=i(r);if(206===n){var o=r.getResponseHeader("Content-Range"),c=/bytes (\d+)-(\d+)\/(\d+)/.exec(o),l=parseInt(c[1],10);a.onDone({begin:l,chunk:s})}else a.onProgressiveData?a.onDone(null):s?a.onDone({begin:0,chunk:s}):a.onError&&a.onError(r.status)}else a.onError&&a.onError(r.status)}}}},hasPendingRequests:function(){for(var e in this.pendingRequests)return!0;return!1},getRequestXhr:function(e){return this.pendingRequests[e].xhr},isStreamingRequest:function(e){return!!this.pendingRequests[e].onProgressiveData},isPendingRequest:function(e){return e in this.pendingRequests},isLoadedRequest:function(e){return e in this.loadedRequests},abortAllRequests:function(){for(var e in this.pendingRequests)this.abortRequest(0|e)},abortRequest:function(e){var t=this.pendingRequests[e].xhr;delete this.pendingRequests[e];t.abort()}};var f=c.assert,d=c.createPromiseCapability,g=c.isInt,p=c.MissingPDFException,m=c.UnexpectedResponseException;n.prototype={_onRangeRequestReaderClosed:function(e){var t=this._rangeRequestReaders.indexOf(e);t>=0&&this._rangeRequestReaders.splice(t,1)},getFullReader:function(){f(!this._fullRequestReader);this._fullRequestReader=new s(this._manager,this._options);return this._fullRequestReader},getRangeReader:function(e,t){var a=new o(this._manager,e,t);a.onClosed=this._onRangeRequestReaderClosed.bind(this);this._rangeRequestReaders.push(a);return a},cancelAllRequests:function(e){this._fullRequestReader&&this._fullRequestReader.cancel(e);this._rangeRequestReaders.slice(0).forEach(function(t){t.cancel(e)})}};s.prototype={_validateRangeRequestCapabilities:function(){if(this._disableRange)return!1;var e=this._manager;if(!e.isHttp)return!1;var t=this._fullRequestId,a=e.getRequestXhr(t);if("bytes"!==a.getResponseHeader("Accept-Ranges"))return!1;if("identity"!==(a.getResponseHeader("Content-Encoding")||"identity"))return!1;var r=a.getResponseHeader("Content-Length");r=parseInt(r,10);if(!g(r))return!1;this._contentLength=r;return!(r<=2*this._rangeChunkSize)},_onHeadersReceived:function(){this._validateRangeRequestCapabilities()&&(this._isRangeSupported=!0);var e=this._manager,t=this._fullRequestId;e.isStreamingRequest(t)?this._isStreamingSupported=!0:this._isRangeSupported&&e.abortRequest(t);this._headersReceivedCapability.resolve()},_onProgressiveData:function(e){if(this._requests.length>0){this._requests.shift().resolve({value:e,done:!1})}else this._cachedChunks.push(e)},_onDone:function(e){e&&this._onProgressiveData(e.chunk);this._done=!0;if(!(this._cachedChunks.length>0)){this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}},_onError:function(e){var t,a=this._url;t=404===e||0===e&&/^file:/.test(a)?new p('Missing PDF "'+a+'".'):new m("Unexpected server response ("+e+') while retrieving PDF "'+a+'".',e);this._storedError=t;this._headersReceivedCapability.reject(t);this._requests.forEach(function(e){e.reject(t)});this._requests=[];this._cachedChunks=[]},_onProgress:function(e){this.onProgress&&this.onProgress({loaded:e.loaded,total:e.lengthComputable?e.total:this._contentLength})},get isRangeSupported(){return this._isRangeSupported},get isStreamingSupported(){return this._isStreamingSupported},get contentLength(){return this._contentLength},get headersReady(){return this._headersReceivedCapability.promise},read:function(){if(this._storedError)return Promise.reject(this._storedError);if(this._cachedChunks.length>0){var e=this._cachedChunks.shift();return Promise.resolve(e)}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=d();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._headersReceivedCapability.reject(e);this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._manager.isPendingRequest(this._fullRequestId)&&this._manager.abortRequest(this._fullRequestId);this._fullRequestReader=null}};o.prototype={_close:function(){this.onClosed&&this.onClosed(this)},_onDone:function(e){var t=e.chunk;if(this._requests.length>0){this._requests.shift().resolve({value:t,done:!1})}else this._queuedChunk=t;this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._close()},_onProgress:function(e){!this.isStreamingSupported&&this.onProgress&&this.onProgress({loaded:e.loaded})},get isStreamingSupported(){return!1},read:function(){if(null!==this._queuedChunk){var e=this._queuedChunk;this._queuedChunk=null;return Promise.resolve({value:e,done:!1})}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=d();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._manager.isPendingRequest(this._requestId)&&this._manager.abortRequest(this._requestId);this._close()}};l.setPDFNetworkStreamClass(n);t.PDFNetworkStream=n;t.NetworkManager=r},function(e,t,a){"use strict";function r(){}var i=a(0),n=a(1),s=a(2),o=a(3),c=a(16),l=a(14),h=i.AnnotationBorderStyleType,u=i.AnnotationFieldFlag,f=i.AnnotationFlag,d=i.AnnotationType,g=i.OPS,p=i.Util,m=i.isArray,b=i.isInt,v=i.stringToBytes,y=i.stringToPDFString,k=i.warn,w=n.Dict,C=n.isDict,x=n.isName,S=n.isRef,A=n.isStream,I=s.Stream,B=o.ColorSpace,R=c.Catalog,T=c.ObjectLoader,O=c.FileSpec,P=l.OperatorList;r.prototype={create:function(e,t,a,r){var i=e.fetchIfRef(t);if(C(i)){var n=S(t)?t.toString():"annot_"+r.createObjId(),s=i.get("Subtype");s=x(s)?s.name:null;var o={xref:e,dict:i,ref:S(t)?t:null,subtype:s,id:n,pdfManager:a};switch(s){case"Link":return new N(o);case"Text":return new U(o);case"Widget":var c=p.getInheritableProperty(i,"FT");c=x(c)?c.name:null;switch(c){case"Tx":return new D(o);case"Btn":return new F(o);case"Ch":return new q(o)}k('Unimplemented widget field type "'+c+'", falling back to base field type.');return new L(o);case"Popup":return new j(o);case"Highlight":return new _(o);case"Underline":return new z(o);case"Squiggly":return new H(o);case"StrikeOut":return new G(o);case"FileAttachment":return new X(o);default:k(s?'Unimplemented annotation type "'+s+'", falling back to base annotation.':"Annotation is missing the required /Subtype.");return new M(o)}}}};var M=function(){function e(e,t,a){var r=p.getAxialAlignedBoundingBox(t,a),i=r[0],n=r[1],s=r[2],o=r[3];if(i===s||n===o)return[1,0,0,1,e[0],e[1]];var c=(e[2]-e[0])/(s-i),l=(e[3]-e[1])/(o-n);return[c,0,0,l,e[0]-i*c,e[1]-n*l]}function t(e){var t=e.dict;this.setFlags(t.get("F"));this.setRectangle(t.getArray("Rect"));this.setColor(t.getArray("C"));this.setBorderStyle(t);this.setAppearance(t);this.data={};this.data.id=e.id;this.data.subtype=e.subtype;this.data.annotationFlags=this.flags;this.data.rect=this.rectangle;this.data.color=this.color;this.data.borderStyle=this.borderStyle;this.data.hasAppearance=!!this.appearance}t.prototype={_hasFlag:function(e,t){return!!(e&t)},_isViewable:function(e){return!this._hasFlag(e,f.INVISIBLE)&&!this._hasFlag(e,f.HIDDEN)&&!this._hasFlag(e,f.NOVIEW)},_isPrintable:function(e){return this._hasFlag(e,f.PRINT)&&!this._hasFlag(e,f.INVISIBLE)&&!this._hasFlag(e,f.HIDDEN)},get viewable(){return 0===this.flags||this._isViewable(this.flags)},get printable(){return 0!==this.flags&&this._isPrintable(this.flags)},setFlags:function(e){this.flags=b(e)&&e>0?e:0},hasFlag:function(e){return this._hasFlag(this.flags,e)},setRectangle:function(e){m(e)&&4===e.length?this.rectangle=p.normalizeRect(e):this.rectangle=[0,0,0,0]},setColor:function(e){var t=new Uint8Array(3);if(m(e))switch(e.length){case 0:this.color=null;break;case 1:B.singletons.gray.getRgbItem(e,0,t,0);this.color=t;break;case 3:B.singletons.rgb.getRgbItem(e,0,t,0);this.color=t;break;case 4:B.singletons.cmyk.getRgbItem(e,0,t,0);this.color=t;break;default:this.color=t}else this.color=t},setBorderStyle:function(e){this.borderStyle=new E;if(C(e))if(e.has("BS")){var t=e.get("BS"),a=t.get("Type");if(!a||x(a,"Border")){this.borderStyle.setWidth(t.get("W"));this.borderStyle.setStyle(t.get("S"));this.borderStyle.setDashArray(t.getArray("D"))}}else if(e.has("Border")){var r=e.getArray("Border");if(m(r)&&r.length>=3){this.borderStyle.setHorizontalCornerRadius(r[0]);this.borderStyle.setVerticalCornerRadius(r[1]);this.borderStyle.setWidth(r[2]);4===r.length&&this.borderStyle.setDashArray(r[3])}}else this.borderStyle.setWidth(0)},setAppearance:function(e){this.appearance=null;var t=e.get("AP");if(C(t)){var a=t.get("N");if(A(a))this.appearance=a;else if(C(a)){var r=e.get("AS");x(r)&&a.has(r.name)&&(this.appearance=a.get(r.name))}}},_preparePopup:function(e){e.has("C")||(this.data.color=null);this.data.hasPopup=e.has("Popup");this.data.title=y(e.get("T")||"");this.data.contents=y(e.get("Contents")||"")},loadResources:function(e){return new Promise(function(t,a){this.appearance.dict.getAsync("Resources").then(function(r){if(r){new T(r.map,e,r.xref).load().then(function(){t(r)},a)}else t()},a)}.bind(this))},getOperatorList:function(t,a,r){if(!this.appearance)return Promise.resolve(new P);var i=this.data,n=this.appearance.dict,s=this.loadResources(["ExtGState","ColorSpace","Pattern","Shading","XObject","Font"]),o=n.getArray("BBox")||[0,0,1,1],c=n.getArray("Matrix")||[1,0,0,1,0,0],l=e(i.rect,o,c),h=this;return s.then(function(e){var r=new P;r.addOp(g.beginAnnotation,[i.rect,l,c]);return t.getOperatorList(h.appearance,a,e,r).then(function(){r.addOp(g.endAnnotation,[]);h.appearance.reset();return r})})}};return t}(),E=function(){function e(){this.width=1;this.style=h.SOLID;this.dashArray=[3];this.horizontalCornerRadius=0;this.verticalCornerRadius=0}e.prototype={setWidth:function(e){e===(0|e)&&(this.width=e)},setStyle:function(e){if(e)switch(e.name){case"S":this.style=h.SOLID;break;case"D":this.style=h.DASHED;break;case"B":this.style=h.BEVELED;break;case"I":this.style=h.INSET;break;case"U":this.style=h.UNDERLINE}},setDashArray:function(e){if(m(e)&&e.length>0){for(var t=!0,a=!0,r=0,i=e.length;r<i;r++){var n=e[r];if(!(+n>=0)){t=!1;break}n>0&&(a=!1)}t&&!a?this.dashArray=e:this.width=0}else e&&(this.width=0)},setHorizontalCornerRadius:function(e){e===(0|e)&&(this.horizontalCornerRadius=e)},setVerticalCornerRadius:function(e){e===(0|e)&&(this.verticalCornerRadius=e)}};return e}(),L=function(){function e(e){M.call(this,e);var t=e.dict,a=this.data;a.annotationType=d.WIDGET;a.fieldName=this._constructFieldName(t);a.fieldValue=p.getInheritableProperty(t,"V",!0);a.alternativeText=y(t.get("TU")||"");a.defaultAppearance=p.getInheritableProperty(t,"DA")||"";var r=p.getInheritableProperty(t,"FT");a.fieldType=x(r)?r.name:null;this.fieldResources=p.getInheritableProperty(t,"DR")||w.empty;a.fieldFlags=p.getInheritableProperty(t,"Ff");(!b(a.fieldFlags)||a.fieldFlags<0)&&(a.fieldFlags=0);a.readOnly=this.hasFieldFlag(u.READONLY);"Sig"===a.fieldType&&this.setFlags(f.HIDDEN)}p.inherit(e,M,{_constructFieldName:function(e){if(!e.has("T")&&!e.has("Parent")){k("Unknown field name, falling back to empty field name.");return""}if(!e.has("Parent"))return y(e.get("T"));var t=[];e.has("T")&&t.unshift(y(e.get("T")));for(var a=e;a.has("Parent");){a=a.get("Parent");if(!C(a))break;a.has("T")&&t.unshift(y(a.get("T")))}return t.join(".")},hasFieldFlag:function(e){return!!(this.data.fieldFlags&e)}});return e}(),D=function(){function e(e){L.call(this,e);this.data.fieldValue=y(this.data.fieldValue||"");var t=p.getInheritableProperty(e.dict,"Q");(!b(t)||t<0||t>2)&&(t=null);this.data.textAlignment=t;var a=p.getInheritableProperty(e.dict,"MaxLen");(!b(a)||a<0)&&(a=null);this.data.maxLen=a;this.data.multiLine=this.hasFieldFlag(u.MULTILINE);this.data.comb=this.hasFieldFlag(u.COMB)&&!this.hasFieldFlag(u.MULTILINE)&&!this.hasFieldFlag(u.PASSWORD)&&!this.hasFieldFlag(u.FILESELECT)&&null!==this.data.maxLen}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;if(a)return Promise.resolve(r);if(this.appearance)return M.prototype.getOperatorList.call(this,e,t,a);if(!this.data.defaultAppearance)return Promise.resolve(r);var i=new I(v(this.data.defaultAppearance));return e.getOperatorList(i,t,this.fieldResources,r).then(function(){return r})}});return e}(),F=function(){function e(e){L.call(this,e);this.data.checkBox=!this.hasFieldFlag(u.RADIO)&&!this.hasFieldFlag(u.PUSHBUTTON);if(this.data.checkBox){if(!x(this.data.fieldValue))return;this.data.fieldValue=this.data.fieldValue.name}this.data.radioButton=this.hasFieldFlag(u.RADIO)&&!this.hasFieldFlag(u.PUSHBUTTON);if(this.data.radioButton){this.data.fieldValue=this.data.buttonValue=null;var t=e.dict.get("Parent");if(C(t)&&t.has("V")){var a=t.get("V");x(a)&&(this.data.fieldValue=a.name)}var r=e.dict.get("AP");if(!C(r))return;var i=r.get("N");if(!C(i))return;for(var n=i.getKeys(),s=0,o=n.length;s<o;s++)if("Off"!==n[s]){this.data.buttonValue=n[s];break}}}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;return a?Promise.resolve(r):this.appearance?M.prototype.getOperatorList.call(this,e,t,a):Promise.resolve(r)}});return e}(),q=function(){function e(e){L.call(this,e);this.data.options=[];var t=p.getInheritableProperty(e.dict,"Opt");if(m(t))for(var a=e.xref,r=0,i=t.length;r<i;r++){var n=a.fetchIfRef(t[r]),s=m(n);this.data.options[r]={exportValue:s?a.fetchIfRef(n[0]):n,displayValue:s?a.fetchIfRef(n[1]):n}}m(this.data.fieldValue)||(this.data.fieldValue=[this.data.fieldValue]);this.data.combo=this.hasFieldFlag(u.COMBO);this.data.multiSelect=this.hasFieldFlag(u.MULTISELECT)}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;return a?Promise.resolve(r):M.prototype.getOperatorList.call(this,e,t,a)}});return e}(),U=function(){function e(e){M.call(this,e);this.data.annotationType=d.TEXT;if(this.data.hasAppearance)this.data.name="NoIcon";else{this.data.rect[1]=this.data.rect[3]-t;this.data.rect[2]=this.data.rect[0]+t;this.data.name=e.dict.has("Name")?e.dict.get("Name").name:"Note"}this._preparePopup(e.dict)}var t=22;p.inherit(e,M,{});return e}(),N=function(){function e(e){M.call(this,e);var t=this.data;t.annotationType=d.LINK;R.parseDestDictionary({destDict:e.dict,resultObj:t,docBaseUrl:e.pdfManager.docBaseUrl})}p.inherit(e,M,{});return e}(),j=function(){function e(e){M.call(this,e);this.data.annotationType=d.POPUP;var t=e.dict,a=t.get("Parent");if(a){this.data.parentId=t.getRaw("Parent").toString();this.data.title=y(a.get("T")||"");this.data.contents=y(a.get("Contents")||"");if(a.has("C")){this.setColor(a.getArray("C"));this.data.color=this.color}else this.data.color=null;if(!this.viewable){var r=a.get("F");this._isViewable(r)&&this.setFlags(r)}}else k("Popup annotation has a missing or invalid parent annotation.")}p.inherit(e,M,{});return e}(),_=function(){function e(e){M.call(this,e);this.data.annotationType=d.HIGHLIGHT;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),z=function(){function e(e){M.call(this,e);this.data.annotationType=d.UNDERLINE;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),H=function(){function e(e){M.call(this,e);this.data.annotationType=d.SQUIGGLY;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),G=function(){function e(e){M.call(this,e);this.data.annotationType=d.STRIKEOUT;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),X=function(){function e(e){M.call(this,e);var t=new O(e.dict.get("FS"),e.xref);this.data.annotationType=d.FILEATTACHMENT;this.data.file=t.serializable;this._preparePopup(e.dict)}p.inherit(e,M,{});return e}();t.Annotation=M;t.AnnotationBorderStyle=E;t.AnnotationFactory=r},function(e,t,a){"use strict";function r(e){return 0!=(1&e)}function i(e){return 0==(1&e)}function n(e,t,a){for(var r=t,i=e.length;r<i;++r)if(e[r]!==a)return r;return r}function s(e,t,a,r){for(var i=t;i<a;++i)e[i]=r}function o(e,t,a){for(var r=t,i=a-1;r<i;++r,--i){var n=e[r];e[r]=e[i];e[i]=n}}function c(e,t,a){return{str:e,dir:a?"ttb":t?"ltr":"rtl"}}function l(e,t,a){var l=!0,h=e.length;if(0===h||a)return c(e,l,a);g.length=h;p.length=h;var m,b,v=0;for(m=0;m<h;++m){g[m]=e.charAt(m);var y=e.charCodeAt(m),k="L";if(y<=255)k=f[y];else if(1424<=y&&y<=1524)k="R";else if(1536<=y&&y<=1791){k=d[255&y];k||u("Bidi: invalid Unicode character "+y.toString(16))}else 1792<=y&&y<=2220&&(k="AL");"R"!==k&&"AL"!==k&&"AN"!==k||v++;p[m]=k}if(0===v){l=!0;return c(e,l)}if(-1===t)if(v/h<.3){l=!0;t=0}else{l=!1;t=1}var w=[];for(m=0;m<h;++m)w[m]=t;var C=r(t)?"R":"L",x=C,S=x,A=x;for(m=0;m<h;++m)"NSM"===p[m]?p[m]=A:A=p[m];A=x;var I;for(m=0;m<h;++m){I=p[m];"EN"===I?p[m]="AL"===A?"AN":"EN":"R"!==I&&"L"!==I&&"AL"!==I||(A=I)}for(m=0;m<h;++m){I=p[m];"AL"===I&&(p[m]="R")}for(m=1;m<h-1;++m){"ES"===p[m]&&"EN"===p[m-1]&&"EN"===p[m+1]&&(p[m]="EN");"CS"!==p[m]||"EN"!==p[m-1]&&"AN"!==p[m-1]||p[m+1]!==p[m-1]||(p[m]=p[m-1])}for(m=0;m<h;++m)if("EN"===p[m]){var B;for(B=m-1;B>=0&&"ET"===p[B];--B)p[B]="EN";for(B=m+1;B<h&&"ET"===p[B];++B)p[B]="EN"}for(m=0;m<h;++m){I=p[m];"WS"!==I&&"ES"!==I&&"ET"!==I&&"CS"!==I||(p[m]="ON")}A=x;for(m=0;m<h;++m){I=p[m];"EN"===I?p[m]="L"===A?"L":"EN":"R"!==I&&"L"!==I||(A=I)}for(m=0;m<h;++m)if("ON"===p[m]){var R=n(p,m+1,"ON"),T=x;m>0&&(T=p[m-1]);var O=S;R+1<h&&(O=p[R+1]);"L"!==T&&(T="R");"L"!==O&&(O="R");T===O&&s(p,m,R,T);m=R-1}for(m=0;m<h;++m)"ON"===p[m]&&(p[m]=C);for(m=0;m<h;++m){I=p[m];i(w[m])?"R"===I?w[m]+=1:"AN"!==I&&"EN"!==I||(w[m]+=2):"L"!==I&&"AN"!==I&&"EN"!==I||(w[m]+=1)}var P,M=-1,E=99;for(m=0,b=w.length;m<b;++m){P=w[m];M<P&&(M=P);E>P&&r(P)&&(E=P)}for(P=M;P>=E;--P){var L=-1;for(m=0,b=w.length;m<b;++m)if(w[m]<P){if(L>=0){o(g,L,m);L=-1}}else L<0&&(L=m);L>=0&&o(g,L,w.length)}for(m=0,b=g.length;m<b;++m){var D=g[m];"<"!==D&&">"!==D||(g[m]="")}return c(g.join(""),l)} +var h=a(0),u=h.warn,f=["BN","BN","BN","BN","BN","BN","BN","BN","BN","S","B","S","WS","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","B","B","B","S","WS","ON","ON","ET","ET","ET","ON","ON","ON","ON","ON","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","ON","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","ON","ON","ON","BN","BN","BN","BN","BN","BN","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","CS","ON","ET","ET","ET","ET","ON","ON","ON","ON","L","ON","ON","BN","ON","ON","ET","ET","EN","EN","ON","L","ON","ON","ON","EN","L","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","L","L","L","L","L","L","L","L"],d=["AN","AN","AN","AN","AN","AN","ON","ON","AL","ET","ET","AL","CS","AL","ON","ON","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","AN","AN","AN","AN","AN","AN","AN","AN","AN","ET","AN","AN","AL","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","ON","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","NSM","NSM","ON","NSM","NSM","NSM","NSM","AL","AL","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","AL","AL","AL","AL","AL","AL"],g=[],p=[];t.bidi=l},function(e,t,a){"use strict";var r=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron"],i=[".notdef","space","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],n=[".notdef","space","dollaroldstyle","dollarsuperior","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","hyphensuperior","colonmonetary","onefitted","rupiah","centoldstyle","figuredash","hypheninferior","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior"];t.ISOAdobeCharset=r;t.ExpertCharset=i;t.ExpertSubsetCharset=n},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(5),o=r.Util,c=r.assert,l=r.warn,h=r.error,u=r.isInt,f=r.isString,d=r.MissingDataException,g=r.CMapCompressionType,p=i.isEOF,m=i.isName,b=i.isCmd,v=i.isStream,y=n.Stream,k=s.Lexer,w=["Adobe-GB1-UCS2","Adobe-CNS1-UCS2","Adobe-Japan1-UCS2","Adobe-Korea1-UCS2","78-EUC-H","78-EUC-V","78-H","78-RKSJ-H","78-RKSJ-V","78-V","78ms-RKSJ-H","78ms-RKSJ-V","83pv-RKSJ-H","90ms-RKSJ-H","90ms-RKSJ-V","90msp-RKSJ-H","90msp-RKSJ-V","90pv-RKSJ-H","90pv-RKSJ-V","Add-H","Add-RKSJ-H","Add-RKSJ-V","Add-V","Adobe-CNS1-0","Adobe-CNS1-1","Adobe-CNS1-2","Adobe-CNS1-3","Adobe-CNS1-4","Adobe-CNS1-5","Adobe-CNS1-6","Adobe-GB1-0","Adobe-GB1-1","Adobe-GB1-2","Adobe-GB1-3","Adobe-GB1-4","Adobe-GB1-5","Adobe-Japan1-0","Adobe-Japan1-1","Adobe-Japan1-2","Adobe-Japan1-3","Adobe-Japan1-4","Adobe-Japan1-5","Adobe-Japan1-6","Adobe-Korea1-0","Adobe-Korea1-1","Adobe-Korea1-2","B5-H","B5-V","B5pc-H","B5pc-V","CNS-EUC-H","CNS-EUC-V","CNS1-H","CNS1-V","CNS2-H","CNS2-V","ETHK-B5-H","ETHK-B5-V","ETen-B5-H","ETen-B5-V","ETenms-B5-H","ETenms-B5-V","EUC-H","EUC-V","Ext-H","Ext-RKSJ-H","Ext-RKSJ-V","Ext-V","GB-EUC-H","GB-EUC-V","GB-H","GB-V","GBK-EUC-H","GBK-EUC-V","GBK2K-H","GBK2K-V","GBKp-EUC-H","GBKp-EUC-V","GBT-EUC-H","GBT-EUC-V","GBT-H","GBT-V","GBTpc-EUC-H","GBTpc-EUC-V","GBpc-EUC-H","GBpc-EUC-V","H","HKdla-B5-H","HKdla-B5-V","HKdlb-B5-H","HKdlb-B5-V","HKgccs-B5-H","HKgccs-B5-V","HKm314-B5-H","HKm314-B5-V","HKm471-B5-H","HKm471-B5-V","HKscs-B5-H","HKscs-B5-V","Hankaku","Hiragana","KSC-EUC-H","KSC-EUC-V","KSC-H","KSC-Johab-H","KSC-Johab-V","KSC-V","KSCms-UHC-H","KSCms-UHC-HW-H","KSCms-UHC-HW-V","KSCms-UHC-V","KSCpc-EUC-H","KSCpc-EUC-V","Katakana","NWP-H","NWP-V","RKSJ-H","RKSJ-V","Roman","UniCNS-UCS2-H","UniCNS-UCS2-V","UniCNS-UTF16-H","UniCNS-UTF16-V","UniCNS-UTF32-H","UniCNS-UTF32-V","UniCNS-UTF8-H","UniCNS-UTF8-V","UniGB-UCS2-H","UniGB-UCS2-V","UniGB-UTF16-H","UniGB-UTF16-V","UniGB-UTF32-H","UniGB-UTF32-V","UniGB-UTF8-H","UniGB-UTF8-V","UniJIS-UCS2-H","UniJIS-UCS2-HW-H","UniJIS-UCS2-HW-V","UniJIS-UCS2-V","UniJIS-UTF16-H","UniJIS-UTF16-V","UniJIS-UTF32-H","UniJIS-UTF32-V","UniJIS-UTF8-H","UniJIS-UTF8-V","UniJIS2004-UTF16-H","UniJIS2004-UTF16-V","UniJIS2004-UTF32-H","UniJIS2004-UTF32-V","UniJIS2004-UTF8-H","UniJIS2004-UTF8-V","UniJISPro-UCS2-HW-V","UniJISPro-UCS2-V","UniJISPro-UTF8-V","UniJISX0213-UTF32-H","UniJISX0213-UTF32-V","UniJISX02132004-UTF32-H","UniJISX02132004-UTF32-V","UniKS-UCS2-H","UniKS-UCS2-V","UniKS-UTF16-H","UniKS-UTF16-V","UniKS-UTF32-H","UniKS-UTF32-V","UniKS-UTF8-H","UniKS-UTF8-V","V","WP-Symbol"],C=function(){function e(e){this.codespaceRanges=[[],[],[],[]];this.numCodespaceRanges=0;this._map=[];this.name="";this.vertical=!1;this.useCMap=null;this.builtInCMap=e}e.prototype={addCodespaceRange:function(e,t,a){this.codespaceRanges[e-1].push(t,a);this.numCodespaceRanges++},mapCidRange:function(e,t,a){for(;e<=t;)this._map[e++]=a++},mapBfRange:function(e,t,a){for(var r=a.length-1;e<=t;){this._map[e++]=a;a=a.substr(0,r)+String.fromCharCode(a.charCodeAt(r)+1)}},mapBfRangeToArray:function(e,t,a){for(var r=0,i=a.length;e<=t&&r<i;){this._map[e]=a[r++];++e}},mapOne:function(e,t){this._map[e]=t},lookup:function(e){return this._map[e]},contains:function(e){return void 0!==this._map[e]},forEach:function(e){var t,a=this._map,r=a.length;if(r<=65536)for(t=0;t<r;t++)void 0!==a[t]&&e(t,a[t]);else for(t in this._map)e(t,a[t])},charCodeOf:function(e){return this._map.indexOf(e)},getMap:function(){return this._map},readCharCode:function(e,t,a){for(var r=0,i=this.codespaceRanges,n=this.codespaceRanges.length,s=0;s<n;s++){r=(r<<8|e.charCodeAt(t+s))>>>0;for(var o=i[s],c=0,l=o.length;c<l;){var h=o[c++],u=o[c++];if(r>=h&&r<=u){a.charcode=r;a.length=s+1;return}}}a.charcode=0;a.length=1},get length(){return this._map.length},get isIdentityCMap(){if("Identity-H"!==this.name&&"Identity-V"!==this.name)return!1;if(65536!==this._map.length)return!1;for(var e=0;e<65536;e++)if(this._map[e]!==e)return!1;return!0}};return e}(),x=function(){function e(e,t){C.call(this);this.vertical=e;this.addCodespaceRange(t,0,65535)}o.inherit(e,C,{});e.prototype={addCodespaceRange:C.prototype.addCodespaceRange,mapCidRange:function(e,t,a){h("should not call mapCidRange")},mapBfRange:function(e,t,a){h("should not call mapBfRange")},mapBfRangeToArray:function(e,t,a){h("should not call mapBfRangeToArray")},mapOne:function(e,t){h("should not call mapCidOne")},lookup:function(e){return u(e)&&e<=65535?e:void 0},contains:function(e){return u(e)&&e<=65535},forEach:function(e){for(var t=0;t<=65535;t++)e(t,t)},charCodeOf:function(e){return u(e)&&e<=65535?e:-1},getMap:function(){for(var e=new Array(65536),t=0;t<=65535;t++)e[t]=t;return e},readCharCode:C.prototype.readCharCode,get length(){return 65536},get isIdentityCMap(){h("should not access .isIdentityCMap")}};return e}(),S=function(){function e(e,t){for(var a=0,r=0;r<=t;r++)a=a<<8|e[r];return a>>>0}function t(e,t){return 1===t?String.fromCharCode(e[0],e[1]):3===t?String.fromCharCode(e[0],e[1],e[2],e[3]):String.fromCharCode.apply(null,e.subarray(0,t+1))}function a(e,t,a){for(var r=0,i=a;i>=0;i--){r+=e[i]+t[i];e[i]=255&r;r>>=8}}function r(e,t){for(var a=1,r=t;r>=0&&a>0;r--){a+=e[r];e[r]=255&a;a>>=8}}function i(e){this.buffer=e;this.pos=0;this.end=e.length;this.tmpBuf=new Uint8Array(l)}function n(n,s,l){return new Promise(function(h,u){var f=new i(n),d=f.readByte();s.vertical=!!(1&d);for(var g,p,m=null,b=new Uint8Array(o),v=new Uint8Array(o),y=new Uint8Array(o),k=new Uint8Array(o),w=new Uint8Array(o);(p=f.readByte())>=0;){var C=p>>5;if(7!==C){var x=!!(16&p),S=15&p;c(S+1<=o);var A,I=f.readNumber();switch(C){case 0:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);s.addCodespaceRange(S+1,e(b,S),e(v,S));for(A=1;A<I;A++){r(v,S);f.readHexNumber(b,S);a(b,v,S);f.readHexNumber(v,S);a(v,b,S);s.addCodespaceRange(S+1,e(b,S),e(v,S))}break;case 1:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();for(A=1;A<I;A++){r(v,S);f.readHexNumber(b,S);a(b,v,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber()}break;case 2:f.readHex(y,S);g=f.readNumber();s.mapOne(e(y,S),g);for(A=1;A<I;A++){r(y,S);if(!x){f.readHexNumber(w,S);a(y,w,S)}g=f.readSigned()+(g+1);s.mapOne(e(y,S),g)}break;case 3:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();s.mapCidRange(e(b,S),e(v,S),g);for(A=1;A<I;A++){r(v,S);if(x)b.set(v);else{f.readHexNumber(b,S);a(b,v,S)}f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();s.mapCidRange(e(b,S),e(v,S),g)}break;case 4:f.readHex(y,1);f.readHex(k,S);s.mapOne(e(y,1),t(k,S));for(A=1;A<I;A++){r(y,1);if(!x){f.readHexNumber(w,1);a(y,w,1)}r(k,S);f.readHexSigned(w,S);a(k,w,S);s.mapOne(e(y,1),t(k,S))}break;case 5:f.readHex(b,1);f.readHexNumber(v,1);a(v,b,1);f.readHex(k,S);s.mapBfRange(e(b,1),e(v,1),t(k,S));for(A=1;A<I;A++){r(v,1);if(x)b.set(v);else{f.readHexNumber(b,1);a(b,v,1)}f.readHexNumber(v,1);a(v,b,1);f.readHex(k,S);s.mapBfRange(e(b,1),e(v,1),t(k,S))}break;default:u(new Error("processBinaryCMap: Unknown type: "+C));return}}else switch(31&p){case 0:f.readString();break;case 1:m=f.readString()}}h(m?l(m):s)})}function s(){}var o=16,l=19;i.prototype={readByte:function(){return this.pos>=this.end?-1:this.buffer[this.pos++]},readNumber:function(){var e,t=0;do{var a=this.readByte();a<0&&h("unexpected EOF in bcmap");e=!(128&a);t=t<<7|127&a}while(!e);return t},readSigned:function(){var e=this.readNumber();return 1&e?~(e>>>1):e>>>1},readHex:function(e,t){e.set(this.buffer.subarray(this.pos,this.pos+t+1));this.pos+=t+1},readHexNumber:function(e,t){var a,r=this.tmpBuf,i=0;do{var n=this.readByte();n<0&&h("unexpected EOF in bcmap");a=!(128&n);r[i++]=127&n}while(!a);for(var s=t,o=0,c=0;s>=0;){for(;c<8&&r.length>0;){o=r[--i]<<c|o;c+=7}e[s]=255&o;s--;o>>=8;c-=8}},readHexSigned:function(e,t){this.readHexNumber(e,t);for(var a=1&e[t]?255:0,r=0,i=0;i<=t;i++){r=(1&r)<<8|e[i];e[i]=r>>1^a}},readString:function(){for(var e=this.readNumber(),t="",a=0;a<e;a++)t+=String.fromCharCode(this.readNumber());return t}};s.prototype={process:n};return s}(),A=function(){function e(e){for(var t=0,a=0;a<e.length;a++)t=t<<8|e.charCodeAt(a);return t>>>0}function t(e){f(e)||h("Malformed CMap: expected string.")}function a(e){u(e)||h("Malformed CMap: expected int.")}function r(a,r){for(;;){var i=r.getObj();if(p(i))break;if(b(i,"endbfchar"))return;t(i);var n=e(i);i=r.getObj();t(i);var s=i;a.mapOne(n,s)}}function i(a,r){for(;;){var i=r.getObj();if(p(i))break;if(b(i,"endbfrange"))return;t(i);var n=e(i);i=r.getObj();t(i);var s=e(i);i=r.getObj();if(u(i)||f(i)){var o=u(i)?String.fromCharCode(i):i;a.mapBfRange(n,s,o)}else{if(!b(i,"["))break;i=r.getObj();for(var c=[];!b(i,"]")&&!p(i);){c.push(i);i=r.getObj()}a.mapBfRangeToArray(n,s,c)}}h("Invalid bf range.")}function n(r,i){for(;;){var n=i.getObj();if(p(n))break;if(b(n,"endcidchar"))return;t(n);var s=e(n);n=i.getObj();a(n);var o=n;r.mapOne(s,o)}}function s(r,i){for(;;){var n=i.getObj();if(p(n))break;if(b(n,"endcidrange"))return;t(n);var s=e(n);n=i.getObj();t(n);var o=e(n);n=i.getObj();a(n);var c=n;r.mapCidRange(s,o,c)}}function o(t,a){for(;;){var r=a.getObj();if(p(r))break;if(b(r,"endcodespacerange"))return;if(!f(r))break;var i=e(r);r=a.getObj();if(!f(r))break;var n=e(r);t.addCodespaceRange(r.length,i,n)}h("Invalid codespace range.")}function A(e,t){var a=t.getObj();u(a)&&(e.vertical=!!a)}function I(e,t){var a=t.getObj();m(a)&&f(a.name)&&(e.name=a.name)}function B(e,t,a,c){var h,u;e:for(;;)try{var f=t.getObj();if(p(f))break;if(m(f)){"WMode"===f.name?A(e,t):"CMapName"===f.name&&I(e,t);h=f}else if(b(f))switch(f.cmd){case"endcmap":break e;case"usecmap":m(h)&&(u=h.name);break;case"begincodespacerange":o(e,t);break;case"beginbfchar":r(e,t);break;case"begincidchar":n(e,t);break;case"beginbfrange":i(e,t);break;case"begincidrange":s(e,t)}}catch(e){if(e instanceof d)throw e;l("Invalid cMap data: "+e);continue}!c&&u&&(c=u);return c?R(e,a,c):Promise.resolve(e)}function R(e,t,a){return T(a,t).then(function(t){e.useCMap=t;if(0===e.numCodespaceRanges){for(var a=e.useCMap.codespaceRanges,r=0;r<a.length;r++)e.codespaceRanges[r]=a[r].slice();e.numCodespaceRanges=e.useCMap.numCodespaceRanges}e.useCMap.forEach(function(t,a){e.contains(t)||e.mapOne(t,e.useCMap.lookup(t))});return e})}function T(e,t){if("Identity-H"===e)return Promise.resolve(new x(!1,2));if("Identity-V"===e)return Promise.resolve(new x(!0,2));if(-1===w.indexOf(e))return Promise.reject(new Error("Unknown CMap name: "+e));c(t,"Built-in CMap parameters are not provided.");return t(e).then(function(e){var a=e.cMapData,r=e.compressionType,i=new C(!0);if(r===g.BINARY)return(new S).process(a,i,function(e){return R(i,t,e)});c(r===g.NONE,"TODO: Only BINARY/NONE CMap compression is currently supported.");var n=new k(new y(a));return B(i,n,t,null)})}return{create:function(e){var t=e.encoding,a=e.fetchBuiltInCMap,r=e.useCMap;if(m(t))return T(t.name,a);if(v(t)){return B(new C,new k(t),a,r).then(function(e){return e.isIdentityCMap?T(e.name,a):e})}return Promise.reject(new Error("Encoding required."))}}}();t.CMap=C;t.CMapFactory=A;t.IdentityCMap=x},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(16),o=a(5),c=a(13),l=a(14),h=a(20),u=r.OPS,f=r.MissingDataException,d=r.Util,g=r.assert,p=r.error,m=r.info,b=r.isArray,v=r.isArrayBuffer,y=r.isNum,k=r.isString,w=r.shadow,C=r.stringToBytes,x=r.stringToPDFString,S=r.warn,A=r.isSpace,I=i.Dict,B=i.isDict,R=i.isName,T=i.isStream,O=n.NullStream,P=n.Stream,M=n.StreamsSequenceStream,E=s.Catalog,L=s.ObjectLoader,D=s.XRef,F=o.Linearization,q=c.calculateMD5,U=l.OperatorList,N=l.PartialEvaluator,j=h.AnnotationFactory,_=function(){function e(e,t){return"display"===t&&e.viewable||"print"===t&&e.printable}function t(e,t,a,r,i,n,s){this.pdfManager=e;this.pageIndex=a;this.pageDict=r;this.xref=t;this.ref=i;this.fontCache=n;this.builtInCMapCache=s;this.evaluatorOptions=e.evaluatorOptions;this.resourcesPromise=null;var o="p"+this.pageIndex+"_",c={obj:0};this.idFactory={createObjId:function(){return o+ ++c.obj}}}var a=[0,0,612,792];t.prototype={getPageProp:function(e){return this.pageDict.get(e)},getInheritedPageProp:function(e,t){var a=this.pageDict,r=null,i=0;t=t||!1;for(;a;){var n=t?a.getArray(e):a.get(e);if(void 0!==n){r||(r=[]);r.push(n)}if(++i>100){S("getInheritedPageProp: maximum loop count exceeded for "+e);return r?r[0]:void 0}a=a.get("Parent")}if(r)return 1!==r.length&&B(r[0])?I.merge(this.xref,r):r[0]},get content(){return this.getPageProp("Contents")},get resources(){return w(this,"resources",this.getInheritedPageProp("Resources")||I.empty)},get mediaBox(){var e=this.getInheritedPageProp("MediaBox",!0);return b(e)&&4===e.length?w(this,"mediaBox",e):w(this,"mediaBox",a)},get cropBox(){var e=this.getInheritedPageProp("CropBox",!0);return b(e)&&4===e.length?w(this,"cropBox",e):w(this,"cropBox",this.mediaBox)},get userUnit(){var e=this.getPageProp("UserUnit");(!y(e)||e<=0)&&(e=1);return w(this,"userUnit",e)},get view(){var e=this.mediaBox,t=this.cropBox;if(e===t)return w(this,"view",e);var a=d.intersect(t,e);return w(this,"view",a||e)},get rotate(){var e=this.getInheritedPageProp("Rotate")||0;e%90!=0?e=0:e>=360?e%=360:e<0&&(e=(e%360+360)%360);return w(this,"rotate",e)},getContentStream:function(){var e,t=this.content;if(b(t)){var a,r=this.xref,i=t.length,n=[];for(a=0;a<i;++a)n.push(r.fetchIfRef(t[a]));e=new M(n)}else e=T(t)?t:new O;return e},loadResources:function(e){this.resourcesPromise||(this.resourcesPromise=this.pdfManager.ensure(this,"resources"));return this.resourcesPromise.then(function(){return new L(this.resources.map,e,this.xref).load()}.bind(this))},getOperatorList:function(t,a,r,i){var n=this,s=this.pdfManager,o=s.ensure(this,"getContentStream",[]),c=this.loadResources(["ExtGState","ColorSpace","Pattern","Shading","XObject","Font"]),l=new N(s,this.xref,t,this.pageIndex,this.idFactory,this.fontCache,this.builtInCMapCache,this.evaluatorOptions),h=Promise.all([o,c]),f=h.then(function(e){var i=e[0],s=new U(r,t,n.pageIndex);t.send("StartRenderPage",{transparency:l.hasBlendModes(n.resources),pageIndex:n.pageIndex,intent:r});return l.getOperatorList(i,a,n.resources,s).then(function(){return s})}),d=s.ensure(this,"annotations");return Promise.all([f,d]).then(function(t){var n=t[0],s=t[1];if(0===s.length){n.flush(!0);return n}var o,c,h=[];for(o=0,c=s.length;o<c;o++)e(s[o],r)&&h.push(s[o].getOperatorList(l,a,i));return Promise.all(h).then(function(e){n.addOp(u.beginAnnotations,[]);for(o=0,c=e.length;o<c;o++)n.addOpList(e[o]);n.addOp(u.endAnnotations,[]);n.flush(!0);return n})})},extractTextContent:function(e,t,a,r){var i=this,n=this.pdfManager,s=n.ensure(this,"getContentStream",[]),o=this.loadResources(["ExtGState","XObject","Font"]);return Promise.all([s,o]).then(function(s){var o=s[0];return new N(n,i.xref,e,i.pageIndex,i.idFactory,i.fontCache,i.builtInCMapCache,i.evaluatorOptions).getTextContent(o,t,i.resources,null,a,r)})},getAnnotationsData:function(t){for(var a=this.annotations,r=[],i=0,n=a.length;i<n;++i)t&&!e(a[i],t)||r.push(a[i].data);return r},get annotations(){for(var e=[],t=this.getInheritedPageProp("Annots")||[],a=new j,r=0,i=t.length;r<i;++r){var n=t[r],s=a.create(this.xref,n,this.pdfManager,this.idFactory);s&&e.push(s)}return w(this,"annotations",e)}};return t}(),z=function(){function e(e,t){var a;T(t)?a=t:v(t)?a=new P(t):p("PDFDocument: Unknown argument type");g(a.length>0,"stream must have data");this.pdfManager=e;this.stream=a;this.xref=new D(a,e)}function t(e,t,a,r){var i=e.pos,n=e.end,s=[];i+a>n&&(a=n-i);for(var o=0;o<a;++o)s.push(String.fromCharCode(e.getByte()));var c=s.join("");e.pos=i;var l=r?c.lastIndexOf(t):c.indexOf(t);if(-1===l)return!1;e.pos+=l;return!0}var a={get entries(){return w(this,"entries",{Title:k,Author:k,Subject:k,Keywords:k,Creator:k,Producer:k,CreationDate:k,ModDate:k,Trapped:R})}};e.prototype={parse:function(e){this.setup(e);var t=this.catalog.catDict.get("Version");R(t)&&(this.pdfFormatVersion=t.name);try{this.acroForm=this.catalog.catDict.get("AcroForm");if(this.acroForm){this.xfa=this.acroForm.get("XFA");var a=this.acroForm.get("Fields");a&&b(a)&&0!==a.length||this.xfa||(this.acroForm=null)}}catch(e){if(e instanceof f)throw e;m("Something wrong with AcroForm entry");this.acroForm=null}},get linearization(){var e=null;if(this.stream.length)try{e=F.create(this.stream)}catch(e){if(e instanceof f)throw e;m(e)}return w(this,"linearization",e)},get startXRef(){var e=this.stream,a=0;if(this.linearization){e.reset();t(e,"endobj",1024)&&(a=e.pos+6)}else{for(var r=!1,i=e.end;!r&&i>0;){i-=1024-"startxref".length;i<0&&(i=0);e.pos=i;r=t(e,"startxref",1024,!0)}if(r){e.skip(9);var n;do{n=e.getByte()}while(A(n));for(var s="";n>=32&&n<=57;){s+=String.fromCharCode(n);n=e.getByte()}a=parseInt(s,10);isNaN(a)&&(a=0)}}return w(this,"startXRef",a)},get mainXRefEntriesOffset(){var e=0,t=this.linearization;t&&(e=t.mainXRefEntriesOffset);return w(this,"mainXRefEntriesOffset",e)},checkHeader:function(){var e=this.stream;e.reset();if(t(e,"%PDF-",1024)){e.moveStart();for(var a,r="";(a=e.getByte())>32&&!(r.length>=12);)r+=String.fromCharCode(a);this.pdfFormatVersion||(this.pdfFormatVersion=r.substring(5))}else;},parseStartXRef:function(){var e=this.startXRef;this.xref.setStartXRef(e)},setup:function(e){this.xref.parse(e);var t=this,a={createPage:function(e,a,r,i,n){return new _(t.pdfManager,t.xref,e,a,r,i,n)}};this.catalog=new E(this.pdfManager,this.xref,a)},get numPages(){var e=this.linearization,t=e?e.numPages:this.catalog.numPages;return w(this,"numPages",t)},get documentInfo(){var e,t={PDFFormatVersion:this.pdfFormatVersion,IsAcroFormPresent:!!this.acroForm,IsXFAPresent:!!this.xfa};try{e=this.xref.trailer.get("Info")}catch(e){if(e instanceof f)throw e;m("The document information dictionary is invalid.")}if(e){var r=a.entries;for(var i in r)if(e.has(i)){var n=e.get(i);r[i](n)?t[i]="string"!=typeof n?n:x(n):m('Bad value in document info for "'+i+'"')}}return w(this,"documentInfo",t)},get fingerprint(){var e,t=this.xref,a="",r=t.trailer.get("ID");if(r&&b(r)&&r[0]&&k(r[0])&&"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"!==r[0])e=C(r[0]);else{this.stream.ensureRange&&this.stream.ensureRange(0,Math.min(1024,this.stream.end));e=q(this.stream.bytes.subarray(0,1024),0,1024)}for(var i=0,n=e.length;i<n;i++){var s=e[i].toString(16);a+=1===s.length?"0"+s:s}return w(this,"fingerprint",a)},getPage:function(e){return this.catalog.getPage(e)},cleanup:function(){return this.catalog.cleanup()}};return e}();t.Page=_;t.PDFDocument=z},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(7),s=a(4),o=a(11),c=r.Util,l=r.bytesToString,h=r.error,u=i.Stream,f=n.getGlyphsUnicode,d=s.StandardEncoding,g=o.CFFParser,p=function(){function e(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]}function t(e,t){return e[t]<<8|e[t+1]}function a(a,r,i){var n,s,o,c=1===t(a,r+2)?e(a,r+8):e(a,r+16),l=t(a,r+c);if(4===l){t(a,r+c+2);var u=t(a,r+c+6)>>1;s=r+c+14;n=[];for(o=0;o<u;o++,s+=2)n[o]={end:t(a,s)};s+=2;for(o=0;o<u;o++,s+=2)n[o].start=t(a,s);for(o=0;o<u;o++,s+=2)n[o].idDelta=t(a,s);for(o=0;o<u;o++,s+=2){var f=t(a,s);if(0!==f){n[o].ids=[];for(var d=0,g=n[o].end-n[o].start+1;d<g;d++){n[o].ids[d]=t(a,s+f);f+=2}}}return n}if(12===l){e(a,r+c+4);var p=e(a,r+c+12);s=r+c+16;n=[];for(o=0;o<p;o++){n.push({start:e(a,s),end:e(a,s+4),idDelta:e(a,s+8)-e(a,s)});s+=12}return n}h("not supported cmap: "+l)}function r(e,t,a,r){var i={},n=new g(new u(e,t,a-t),i,r),s=n.parse();return{glyphs:s.charStrings.objects,subrs:s.topDict.privateDict&&s.topDict.privateDict.subrsIndex&&s.topDict.privateDict.subrsIndex.objects,gsubrs:s.globalSubrIndex&&s.globalSubrIndex.objects}}function i(e,t,a){var r,i;if(a){r=4;i=function(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]}}else{r=2;i=function(e,t){return e[t]<<9|e[t+1]<<1}}for(var n=[],s=i(t,0),o=r;o<t.length;o+=r){var c=i(t,o);n.push(e.subarray(s,c));s=c}return n}function n(e,t){for(var a=t.charCodeAt(0),r=0,i=0,n=e.length-1;i<n;){var s=i+n+1>>1;a<e[s].start?n=s-1:i=s}e[i].start<=a&&a<=e[i].end&&(r=e[i].idDelta+(e[i].ids?e[i].ids[a-e[i].start]:a)&65535);return{charCode:a,glyphId:r}}function s(e,t,a){function r(e,a,r,i){t.push({cmd:"quadraticCurveTo",args:[e,a,r,i]})}var i,n=0,o=(e[n]<<24|e[n+1]<<16)>>16,c=0,l=0;n+=10;if(o<0)do{i=e[n]<<8|e[n+1];var h=e[n+2]<<8|e[n+3];n+=4;var u,f;if(1&i){u=(e[n]<<24|e[n+1]<<16)>>16;f=(e[n+2]<<24|e[n+3]<<16)>>16;n+=4}else{u=e[n++];f=e[n++]}if(2&i){c=u;l=f}else{c=0;l=0}var d=1,g=1,p=0,m=0;if(8&i){d=g=(e[n]<<24|e[n+1]<<16)/1073741824;n+=2}else if(64&i){d=(e[n]<<24|e[n+1]<<16)/1073741824;g=(e[n+2]<<24|e[n+3]<<16)/1073741824;n+=4}else if(128&i){d=(e[n]<<24|e[n+1]<<16)/1073741824;p=(e[n+2]<<24|e[n+3]<<16)/1073741824;m=(e[n+4]<<24|e[n+5]<<16)/1073741824;g=(e[n+6]<<24|e[n+7]<<16)/1073741824;n+=8}var b=a.glyphs[h];if(b){t.push({cmd:"save"});t.push({cmd:"transform",args:[d,p,m,g,c,l]});s(b,t,a);t.push({cmd:"restore"})}}while(32&i);else{var v,y,k=[];for(v=0;v<o;v++){k.push(e[n]<<8|e[n+1]);n+=2}n+=2+(e[n]<<8|e[n+1]);for(var w=k[k.length-1]+1,C=[];C.length<w;){i=e[n++];var x=1;8&i&&(x+=e[n++]);for(;x-- >0;)C.push({flags:i})}for(v=0;v<w;v++){switch(18&C[v].flags){case 0:c+=(e[n]<<24|e[n+1]<<16)>>16;n+=2;break;case 2:c-=e[n++];break;case 18:c+=e[n++]}C[v].x=c}for(v=0;v<w;v++){switch(36&C[v].flags){case 0:l+=(e[n]<<24|e[n+1]<<16)>>16;n+=2;break;case 4:l-=e[n++];break;case 36:l+=e[n++]}C[v].y=l}var S=0;for(n=0;n<o;n++){var A=k[n],I=C.slice(S,A+1);if(1&I[0].flags)I.push(I[0]);else if(1&I[I.length-1].flags)I.unshift(I[I.length-1]);else{var B={flags:1,x:(I[0].x+I[I.length-1].x)/2,y:(I[0].y+I[I.length-1].y)/2};I.unshift(B);I.push(B)}!function(e,a){t.push({cmd:"moveTo",args:[e,a]})}(I[0].x,I[0].y);for(v=1,y=I.length;v<y;v++)if(1&I[v].flags)!function(e,a){t.push({cmd:"lineTo",args:[e,a]})}(I[v].x,I[v].y);else if(1&I[v+1].flags){r(I[v].x,I[v].y,I[v+1].x,I[v+1].y);v++}else r(I[v].x,I[v].y,(I[v].x+I[v+1].x)/2,(I[v].y+I[v+1].y)/2);S=A+1}}}function o(e,t,a){function r(e,a){t.push({cmd:"moveTo",args:[e,a]})}function i(e,a){t.push({cmd:"lineTo",args:[e,a]})}function s(e,a,r,i,n,s){t.push({cmd:"bezierCurveTo",args:[e,a,r,i,n,s]})}function c(e){for(var p=0;p<e.length;){var m,b,v,y,k,w,C,x,S,A=!1,I=e[p++];switch(I){case 1:case 3:g+=l.length>>1;A=!0;break;case 4:f+=l.pop();r(u,f);A=!0;break;case 5:for(;l.length>0;){u+=l.shift();f+=l.shift();i(u,f)}break;case 6:for(;l.length>0;){u+=l.shift();i(u,f);if(0===l.length)break;f+=l.shift();i(u,f)}break;case 7:for(;l.length>0;){f+=l.shift();i(u,f);if(0===l.length)break;u+=l.shift();i(u,f)}break;case 8:for(;l.length>0;){m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f)}break;case 10:x=l.pop()+a.subrsBias;S=a.subrs[x];S&&c(S);break;case 11:return;case 12:I=e[p++];switch(I){case 34:m=u+l.shift();b=m+l.shift();k=f+l.shift();u=b+l.shift();s(m,f,b,k,u,k);m=u+l.shift();b=m+l.shift();u=b+l.shift();s(m,k,b,f,u,f);break;case 35:m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);l.pop();break;case 36:m=u+l.shift();k=f+l.shift();b=m+l.shift();w=k+l.shift();u=b+l.shift();s(m,k,b,w,u,w);m=u+l.shift();b=m+l.shift();C=w+l.shift();u=b+l.shift();s(m,w,b,C,u,f);break;case 37:var B=u,R=f;m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b;f=y;Math.abs(u-B)>Math.abs(f-R)?u+=l.shift():f+=l.shift();s(m,v,b,y,u,f);break;default:h("unknown operator: 12 "+I)}break;case 14:if(l.length>=4){var T=l.pop(),O=l.pop();f=l.pop();u=l.pop();t.push({cmd:"save"});t.push({cmd:"translate",args:[u,f]});var P=n(a.cmap,String.fromCharCode(a.glyphNameMap[d[T]]));o(a.glyphs[P.glyphId],t,a);t.push({cmd:"restore"});P=n(a.cmap,String.fromCharCode(a.glyphNameMap[d[O]]));o(a.glyphs[P.glyphId],t,a)}return;case 18:g+=l.length>>1;A=!0;break;case 19:case 20:g+=l.length>>1;p+=g+7>>3;A=!0;break;case 21:f+=l.pop();u+=l.pop();r(u,f);A=!0;break;case 22:u+=l.pop() +;r(u,f);A=!0;break;case 23:g+=l.length>>1;A=!0;break;case 24:for(;l.length>2;){m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f)}u+=l.shift();f+=l.shift();i(u,f);break;case 25:for(;l.length>6;){u+=l.shift();f+=l.shift();i(u,f)}m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);break;case 26:l.length%2&&(u+=l.shift());for(;l.length>0;){m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b;f=y+l.shift();s(m,v,b,y,u,f)}break;case 27:l.length%2&&(f+=l.shift());for(;l.length>0;){m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y;s(m,v,b,y,u,f)}break;case 28:l.push((e[p]<<24|e[p+1]<<16)>>16);p+=2;break;case 29:x=l.pop()+a.gsubrsBias;S=a.gsubrs[x];S&&c(S);break;case 30:for(;l.length>0;){m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+(1===l.length?l.shift():0);s(m,v,b,y,u,f);if(0===l.length)break;m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();f=y+l.shift();u=b+(1===l.length?l.shift():0);s(m,v,b,y,u,f)}break;case 31:for(;l.length>0;){m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();f=y+l.shift();u=b+(1===l.length?l.shift():0);s(m,v,b,y,u,f);if(0===l.length)break;m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+(1===l.length?l.shift():0);s(m,v,b,y,u,f)}break;default:I<32&&h("unknown operator: "+I);if(I<247)l.push(I-139);else if(I<251)l.push(256*(I-247)+e[p++]+108);else if(I<255)l.push(256*-(I-251)-e[p++]-108);else{l.push((e[p]<<24|e[p+1]<<16|e[p+2]<<8|e[p+3])/65536);p+=4}}A&&(l.length=0)}}var l=[],u=0,f=0,g=0;c(e)}function p(e){this.compiledGlyphs=Object.create(null);this.compiledCharCodeToGlyphId=Object.create(null);this.fontMatrix=e}function m(e,t,a){a=a||[488e-6,0,0,488e-6,0,0];p.call(this,a);this.glyphs=e;this.cmap=t}function b(e,t,a,r){a=a||[.001,0,0,.001,0,0];p.call(this,a);this.glyphs=e.glyphs;this.gsubrs=e.gsubrs||[];this.subrs=e.subrs||[];this.cmap=t;this.glyphNameMap=r||f();this.gsubrsBias=this.gsubrs.length<1240?107:this.gsubrs.length<33900?1131:32768;this.subrsBias=this.subrs.length<1240?107:this.subrs.length<33900?1131:32768}p.prototype={getPathJs:function(e){var t=n(this.cmap,e),a=this.compiledGlyphs[t.glyphId];if(!a){a=this.compileGlyph(this.glyphs[t.glyphId]);this.compiledGlyphs[t.glyphId]=a}void 0===this.compiledCharCodeToGlyphId[t.charCode]&&(this.compiledCharCodeToGlyphId[t.charCode]=t.glyphId);return a},compileGlyph:function(e){if(!e||0===e.length||14===e[0])return"";var t=[];t.push({cmd:"save"});t.push({cmd:"transform",args:this.fontMatrix.slice()});t.push({cmd:"scale",args:["size","-size"]});this.compileGlyphImpl(e,t);t.push({cmd:"restore"});return t},compileGlyphImpl:function(){h("Children classes should implement this.")},hasBuiltPath:function(e){var t=n(this.cmap,e);return void 0!==this.compiledGlyphs[t.glyphId]&&void 0!==this.compiledCharCodeToGlyphId[t.charCode]}};c.inherit(m,p,{compileGlyphImpl:function(e,t){s(e,t,this)}});c.inherit(b,p,{compileGlyphImpl:function(e,t){o(e,t,this)}});return{create:function(n,s){for(var o,c,h,u,f,d,g=new Uint8Array(n.data),p=t(g,4),v=0,y=12;v<p;v++,y+=16){var k=l(g.subarray(y,y+4)),w=e(g,y+8),C=e(g,y+12);switch(k){case"cmap":o=a(g,w,w+C);break;case"glyf":c=g.subarray(w,w+C);break;case"loca":h=g.subarray(w,w+C);break;case"head":d=t(g,w+18);f=t(g,w+50);break;case"CFF ":u=r(g,w,w+C,s)}}if(c){var x=d?[1/d,0,0,1/d,0,0]:n.fontMatrix;return new m(i(c,h,f),o,x)}return new b(u,o,n.fontMatrix,n.glyphNameMap)}}}();t.FontRendererFactory=p},function(e,t,a){"use strict";function r(e){if(e.fontMatrix&&e.fontMatrix[0]!==b[0]){var t=.001/e.fontMatrix[0],a=e.widths;for(var r in a)a[r]*=t;e.defaultWidth*=t}}function i(e,t){if(!e.hasIncludedToUnicodeMap&&!(e.hasEncoding||t===e.defaultEncoding||e.toUnicode instanceof ge)){var a=[],r=E();for(var i in t){var n=t[i],s=W(n,r);-1!==s&&(a[i]=String.fromCharCode(s))}e.toUnicode.amend(a)}}function n(e,t){switch(e){case"Type1":return"Type1C"===t?v.TYPE1C:v.TYPE1;case"CIDFontType0":return"CIDFontType0C"===t?v.CIDFONTTYPE0C:v.CIDFONTTYPE0;case"OpenType":return v.OPENTYPE;case"TrueType":return v.TRUETYPE;case"CIDFontType2":return v.CIDFONTTYPE2;case"MMType1":return v.MMTYPE1;case"Type0":return v.TYPE0;default:return v.UNKNOWN}}function s(e,t){if(void 0!==t[e])return e;var a=W(e,t);if(-1!==a)for(var r in t)if(t[r]===a)return r;C("Unable to recover a standard glyph name for: "+e);return e}function o(e,t,a){var r,i,n,o=Object.create(null),c=!!(e.flags&he.Symbolic);if(e.baseEncodingName){n=j(e.baseEncodingName);for(i=0;i<n.length;i++){r=a.indexOf(n[i]);o[i]=r>=0?r:0}}else if(c)for(i in t)o[i]=t[i];else{n=F;for(i=0;i<n.length;i++){r=a.indexOf(n[i]);o[i]=r>=0?r:0}}var l,h=e.differences;if(h)for(i in h){var u=h[i];r=a.indexOf(u);if(-1===r){l||(l=E());var f=s(u,l);f!==u&&(r=a.indexOf(f))}o[i]=r>=0?r:0}return o}var c=a(0),l=(a(1),a(2)),h=a(7),u=a(25),f=a(4),d=a(17),g=a(18),p=a(35),m=a(11),b=c.FONT_IDENTITY_MATRIX,v=c.FontType,y=c.assert,k=c.bytesToString,w=c.error,C=c.info,x=c.isArray,S=c.isInt,A=c.isNum,I=c.readUint32,B=c.shadow,R=c.string32,T=c.warn,O=c.MissingDataException,P=c.isSpace,M=l.Stream,E=h.getGlyphsUnicode,L=h.getDingbatsGlyphsUnicode,D=u.FontRendererFactory,F=f.StandardEncoding,q=f.MacRomanEncoding,U=f.SymbolSetEncoding,N=f.ZapfDingbatsEncoding,j=f.getEncoding,_=d.getStdFontMap,z=d.getNonStdFontMap,H=d.getGlyphMapForStandardFonts,G=d.getSupplementalGlyphMapForArialBlack,X=g.getUnicodeRangeFor,V=g.mapSpecialUnicodeValues,W=g.getUnicodeForGlyph,K=p.Type1Parser,Y=m.CFFStandardStrings,J=m.CFFParser,Z=m.CFFCompiler,Q=m.CFF,$=m.CFFHeader,ee=m.CFFTopDict,te=m.CFFPrivateDict,ae=m.CFFStrings,re=m.CFFIndex,ie=m.CFFCharset,ne=57344,se=63743,oe=!1,ce=1e3,le=!1,he={FixedPitch:1,Serif:2,Symbolic:4,Script:8,Nonsymbolic:32,Italic:64,AllCap:65536,SmallCap:131072,ForceBold:262144},ue=[".notdef",".null","nonmarkingreturn","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron","Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn","thorn","minus","multiply","onesuperior","twosuperior","threesuperior","onehalf","onequarter","threequarters","franc","Gbreve","gbreve","Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron","dcroat"],fe=function(){function e(e,t,a,r,i,n,s,o){this.fontChar=e;this.unicode=t;this.accent=a;this.width=r;this.vmetric=i;this.operatorListId=n;this.isSpace=s;this.isInFont=o}e.prototype.matchesForCache=function(e,t,a,r,i,n,s,o){return this.fontChar===e&&this.unicode===t&&this.accent===a&&this.width===r&&this.vmetric===i&&this.operatorListId===n&&this.isSpace===s&&this.isInFont===o};return e}(),de=function(){function e(e){this._map=e}e.prototype={get length(){return this._map.length},forEach:function(e){for(var t in this._map)e(t,this._map[t].charCodeAt(0))},has:function(e){return void 0!==this._map[e]},get:function(e){return this._map[e]},charCodeOf:function(e){return this._map.indexOf(e)},amend:function(e){for(var t in e)this._map[t]=e[t]}};return e}(),ge=function(){function e(e,t){this.firstChar=e;this.lastChar=t}e.prototype={get length(){return this.lastChar+1-this.firstChar},forEach:function(e){for(var t=this.firstChar,a=this.lastChar;t<=a;t++)e(t,t)},has:function(e){return this.firstChar<=e&&e<=this.lastChar},get:function(e){if(this.firstChar<=e&&e<=this.lastChar)return String.fromCharCode(e)},charCodeOf:function(e){return S(e)&&e>=this.firstChar&&e<=this.lastChar?e:-1},amend:function(e){w("Should not call amend()")}};return e}(),pe=function(){function e(e,t,a){e[t]=a>>8&255;e[t+1]=255&a}function t(e,t,a){e[t]=a>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}function a(e,t,a){var r,i;if(a instanceof Uint8Array)e.set(a,t);else if("string"==typeof a)for(r=0,i=a.length;r<i;r++)e[t++]=255&a.charCodeAt(r);else for(r=0,i=a.length;r<i;r++)e[t++]=255&a[r]}function r(e){this.sfnt=e;this.tables=Object.create(null)}r.getSearchParams=function(e,t){for(var a=1,r=0;(a^e)>a;){a<<=1;r++}var i=a*t;return{range:i,entry:r,rangeShift:t*e-i}};r.prototype={toArray:function(){var i=this.sfnt,n=this.tables,s=Object.keys(n);s.sort();var o,c,l,h,u,f=s.length,d=12+16*f,g=[d];for(o=0;o<f;o++){h=n[s[o]];d+=(h.length+3&-4)>>>0;g.push(d)}var p=new Uint8Array(d);for(o=0;o<f;o++){h=n[s[o]];a(p,g[o],h)}"true"===i&&(i=R(65536));p[0]=255&i.charCodeAt(0);p[1]=255&i.charCodeAt(1);p[2]=255&i.charCodeAt(2);p[3]=255&i.charCodeAt(3);e(p,4,f);var m=r.getSearchParams(f,16);e(p,6,m.range);e(p,8,m.entry);e(p,10,m.rangeShift);d=12;for(o=0;o<f;o++){u=s[o];p[d]=255&u.charCodeAt(0);p[d+1]=255&u.charCodeAt(1);p[d+2]=255&u.charCodeAt(2);p[d+3]=255&u.charCodeAt(3);var b=0;for(c=g[o],l=g[o+1];c<l;c+=4){b=b+I(p,c)>>>0}t(p,d+4,b);t(p,d+8,g[o]);t(p,d+12,n[u].length);d+=16}return p},addTable:function(e,t){if(e in this.tables)throw new Error("Table "+e+" already exists");this.tables[e]=t}};return r}(),me=new Int32Array([0,32,127,161,173,174,1536,1920,2208,4256,6016,6144,7168,7248,8192,8208,8209,8210,8232,8240,8287,8304,9676,9677,12288,12289,43616,43648,65520,65536]),be=function(){function e(e,t,a){var i,s,o;this.name=e;this.loadedName=a.loadedName;this.isType3Font=a.isType3Font;this.sizes=[];this.missingFile=!1;this.glyphCache=Object.create(null);this.isSerifFont=!!(a.flags&he.Serif);this.isSymbolicFont=!!(a.flags&he.Symbolic);this.isMonospace=!!(a.flags&he.FixedPitch);var c=a.type,l=a.subtype;this.type=c;this.fallbackName=this.isMonospace?"monospace":this.isSerifFont?"serif":"sans-serif";this.differences=a.differences;this.widths=a.widths;this.defaultWidth=a.defaultWidth;this.composite=a.composite;this.wideChars=a.wideChars;this.cMap=a.cMap;this.ascent=a.ascent/ce;this.descent=a.descent/ce;this.fontMatrix=a.fontMatrix;this.bbox=a.bbox;this.toUnicode=a.toUnicode;this.toFontChar=[];if("Type3"!==a.type){this.cidEncoding=a.cidEncoding;this.vertical=a.vertical;if(this.vertical){this.vmetrics=a.vmetrics;this.defaultVMetrics=a.defaultVMetrics}var g;if(t&&!t.isEmpty){"Type1C"===l&&("Type1"!==c&&"MMType1"!==c?h(t)?l="TrueType":c="Type1":u(t)&&(c=l="OpenType"));"CIDFontType0C"===l&&"CIDFontType0"!==c&&(c="CIDFontType0");"OpenType"===l&&(c="OpenType");"CIDFontType0"===c&&(f(t)?l="CIDFontType0":u(t)?c=l="OpenType":l="CIDFontType0C");var p;switch(c){case"MMType1":C("MMType1 font ("+e+"), falling back to Type1.");case"Type1":case"CIDFontType0":this.mimetype="font/opentype";var m="Type1C"===l||"CIDFontType0C"===l?new ke(t,a):new ye(e,t,a);r(a);p=this.convert(e,m,a);break;case"OpenType":case"TrueType":case"CIDFontType2":this.mimetype="font/opentype";p=this.checkAndRepair(e,t,a);if(this.isOpenType){r(a);c="OpenType"}break;default:w("Font "+c+" is not supported")}this.data=p;this.fontType=n(c,l);this.fontMatrix=a.fontMatrix;this.widths=a.widths;this.defaultWidth=a.defaultWidth;this.toUnicode=a.toUnicode;this.encoding=a.baseEncoding;this.seacMap=a.seacMap;this.loading=!0}else{t&&T('Font file is empty in "'+e+'" ('+this.loadedName+")");this.missingFile=!0;var b=e.replace(/[,_]/g,"-"),y=_(),k=z(),x=!!y[b]||!(!k[b]||!y[k[b]]);b=y[b]||k[b]||b;this.bold=-1!==b.search(/bold/gi);this.italic=-1!==b.search(/oblique/gi)||-1!==b.search(/italic/gi);this.black=-1!==e.search(/Black/g);this.remeasure=Object.keys(this.widths).length>0;if(x&&"CIDFontType2"===c&&0===a.cidEncoding.indexOf("Identity-")){var S=H(),A=[];for(i in S)A[+i]=S[i];if(/Arial-?Black/i.test(e)){var I=G();for(i in I)A[+i]=I[i]}this.toUnicode instanceof ge||this.toUnicode.forEach(function(e,t){A[+e]=t});this.toFontChar=A;this.toUnicode=new de(A)}else if(/Symbol/i.test(b))this.toFontChar=d(U,E(),a.differences);else if(/Dingbats/i.test(b)){/Wingdings/i.test(e)&&T("Non-embedded Wingdings font, falling back to ZapfDingbats.");this.toFontChar=d(N,L(),a.differences)}else if(x)this.toFontChar=d(a.defaultEncoding,E(),a.differences);else{g=E();this.toUnicode.forEach(function(e,t){if(!this.composite){s=a.differences[e]||a.defaultEncoding[e];o=W(s,g);-1!==o&&(t=o)}this.toFontChar[e]=t}.bind(this))}this.loadedName=b.split("-")[0];this.loading=!1;this.fontType=n(c,l)}}else{for(i=0;i<256;i++)this.toFontChar[i]=this.differences[i]||a.defaultEncoding[i];this.fontType=v.TYPE3}}function t(e,t){return(e<<8)+t}function a(e,t){var a=(e<<8)+t;return 32768&a?a-65536:a}function o(e,t,a,r){return(e<<24)+(t<<16)+(a<<8)+r}function c(e){return String.fromCharCode(e>>8&255,255&e)}function l(e){e=e>32767?32767:e<-32768?-32768:e;return String.fromCharCode(e>>8&255,255&e)}function h(e){var t=e.peekBytes(4);return 65536===I(t,0)}function u(e){var t=e.peekBytes(4);return"OTTO"===k(t)}function f(e){var t=e.peekBytes(2);return 37===t[0]&&33===t[1]||128===t[0]&&1===t[1]}function d(e,t,a){for(var r,i=[],n=0,s=e.length;n<s;n++){r=W(e[n],t);-1!==r&&(i[n]=r)}for(var o in a){r=W(a[o],t);-1!==r&&(i[+o]=r)}return i}function g(e){for(var t=0,a=me.length-1;t<a;){var r=t+a+1>>1;e<me[r]?a=r-1:t=r}return!(1&t)}function p(e,t){var a=t.toUnicode,r=!!(t.flags&he.Symbolic),i=t.toUnicode instanceof ge,n=Object.create(null),s=[],o=[],c=ne;for(var l in e){l|=0;var h=e[l],u=l,f=!1;if(!i&&a.has(l)){f=!0;var d=a.get(u);1===d.length&&(u=d.charCodeAt(0))}if((void 0!==o[u]||g(u)||r&&!f)&&c<=se)do{u=c++;if(oe&&61440===u){u=61472;c=u+1}}while(void 0!==o[u]&&c<=se);n[u]=h;s[l]=u;o[u]=!0}return{toFontChar:s,charCodeToGlyphId:n,nextAvailableFontCharCode:c}}function m(e,t){var a=[];for(var r in e)e[r]>=t||a.push({fontCharCode:0|r,glyphId:e[r]});a.sort(function(e,t){return e.fontCharCode-t.fontCharCode});for(var i=[],n=a.length,s=0;s<n;){var o=a[s].fontCharCode,c=[a[s].glyphId];++s;for(var l=o;s<n&&l+1===a[s].fontCharCode;){c.push(a[s].glyphId);++l;++s;if(65535===l)break}i.push([o,l,c])}return i}function x(e,t){var a,r,i,n,s=m(e,t),o=s[s.length-1][1]>65535?2:1,l="\0\0"+c(o)+"\0\0"+R(4+8*o);for(a=s.length-1;a>=0&&!(s[a][0]<=65535);--a);var h=a+1;s[a][0]<65535&&65535===s[a][1]&&(s[a][1]=65534);var u,f,d,g,p=s[a][1]<65535?1:0,b=h+p,v=pe.getSearchParams(b,2),y="",k="",w="",C="",x="",S=0;for(a=0,r=h;a<r;a++){u=s[a];f=u[0];d=u[1];y+=c(f);k+=c(d);g=u[2];var A=!0;for(i=1,n=g.length;i<n;++i)if(g[i]!==g[i-1]+1){A=!1;break}if(A){w+=c(g[0]-f&65535);C+=c(0)}else{var I=2*(b-a)+2*S;S+=d-f+1;w+=c(0);C+=c(I);for(i=0,n=g.length;i<n;++i)x+=c(g[i])}}if(p>0){k+="ÿÿ";y+="ÿÿ";w+="\0";C+="\0\0"}var B="\0\0"+c(2*b)+c(v.range)+c(v.entry)+c(v.rangeShift)+k+"\0\0"+y+w+C+x,T="",O="";if(o>1){l+="\0\0\n"+R(4+8*o+4+B.length);T="";for(a=0,r=s.length;a<r;a++){u=s[a];f=u[0];g=u[2];var P=g[0];for(i=1,n=g.length;i<n;++i)if(g[i]!==g[i-1]+1){d=u[0]+i-1;T+=R(f)+R(d)+R(P);f=d+1;P=g[i]}T+=R(f)+R(u[1])+R(P)}O="\0\f\0\0"+R(T.length+16)+"\0\0\0\0"+R(T.length/12)}return l+"\0"+c(B.length+4)+B+O+T}function S(e){var t=new M(e.data),a=t.getUint16();t.getBytes(60);var r=t.getUint16();if(a<4&&768&r)return!1;if(t.getUint16()>t.getUint16())return!1;t.getBytes(6);if(0===t.getUint16())return!1;e.data[8]=e.data[9]=0;return!0}function O(e,t,a){a=a||{unitsPerEm:0,yMax:0,yMin:0,ascent:0,descent:0};var r=0,i=0,n=0,s=0,o=null,l=0;if(t)for(var h in t){h|=0;(o>h||!o)&&(o=h);l<h&&(l=h);var u=X(h);u<32?r|=1<<u:u<64?i|=1<<u-32:u<96?n|=1<<u-64:u<123?s|=1<<u-96:w("Unicode ranges Bits > 123 are reserved for internal usage")}else{o=0;l=255}var f=e.bbox||[0,0,0,0],d=a.unitsPerEm||1/(e.fontMatrix||b)[0],g=e.ascentScaled?1:d/ce,p=a.ascent||Math.round(g*(e.ascent||f[3])),m=a.descent||Math.round(g*(e.descent||f[1]));m>0&&e.descent>0&&f[1]<0&&(m=-m);var v=a.yMax||p,y=-a.yMin||-m;return"\0$ô\0\0\0»\0\0\0»\0\0ß\x001\0\0\0\0"+String.fromCharCode(e.fixedPitch?9:0)+"\0\0\0\0\0\0"+R(r)+R(i)+R(n)+R(s)+"*21*"+c(e.italicAngle?1:0)+c(o||e.firstChar)+c(l||e.lastChar)+c(p)+c(m)+"\0d"+c(v)+c(y)+"\0\0\0\0\0\0\0\0"+c(e.xHeight)+c(e.capHeight)+c(0)+c(o||e.firstChar)+"\0"}function P(e){var t=Math.floor(e.italicAngle*Math.pow(2,16));return"\0\0\0"+R(t)+"\0\0\0\0"+R(e.fixedPitch)+"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}function K(e,t){t||(t=[[],[]]);var a,r,i,n,s,o=[t[0][0]||"Original licence",t[0][1]||e,t[0][2]||"Unknown",t[0][3]||"uniqueID",t[0][4]||e,t[0][5]||"Version 0.11",t[0][6]||"",t[0][7]||"Unknown",t[0][8]||"Unknown",t[0][9]||"Unknown"],l=[];for(a=0,r=o.length;a<r;a++){s=t[1][a]||o[a];var h=[];for(i=0,n=s.length;i<n;i++)h.push(c(s.charCodeAt(i)));l.push(h.join(""))}var u=[o,l],f=["\0","\0"],d=["\0\0","\0"],g=["\0\0","\t"],p=o.length*f.length,m="\0\0"+c(p)+c(12*p+6),b=0;for(a=0,r=f.length;a<r;a++){var v=u[a];for(i=0,n=v.length;i<n;i++){s=v[i];m+=f[a]+d[a]+g[a]+c(i)+c(s.length)+c(b);b+=s.length}}m+=o.join("")+l.join("");return m}e.getFontID=function(){var e=1;return function(){return String(e++)}}();e.prototype={name:null,font:null,mimetype:null,encoding:null,get renderer(){var e=D.create(this,le);return B(this,"renderer",e)},exportData:function(){var e={};for(var t in this)this.hasOwnProperty(t)&&(e[t]=this[t]);return e},checkAndRepair:function(e,i,n){function c(e,t,a,r,i,n){if(a-t<=12)return 0;var s=e.subarray(t,a),o=s[0]<<8|s[1];if(32768&o){r.set(s,i);return s.length}var c,l=10,h=0;for(c=0;c<o;c++){h=(s[l]<<8|s[l+1])+1;l+=2}var u=l,f=s[l]<<8|s[l+1];l+=2+f;var d=l,g=0;for(c=0;c<h;c++){var p=s[l++];192&p&&(s[l-1]=63&p);var m=(2&p?1:16&p?0:2)+(4&p?1:32&p?0:2);g+=m;if(8&p){var b=s[l++];c+=b;g+=b*m}}if(0===g)return 0;var v=l+g;if(v>s.length)return 0;if(!n&&f>0){r.set(s.subarray(0,u),i);r.set([0,0],i+u);r.set(s.subarray(d,v),i+u+2);v-=f;s.length-v>3&&(v=v+3&-4);return v}if(s.length-v>3){v=v+3&-4;r.set(s.subarray(0,v),i);return v}r.set(s,i);return s.length}function l(e,t){for(var a,r,i,n,s,o=e.data,c=0,l=0,h=0,f=[],g=[],p=[],m=t.tooComplexToFollowFunctions,b=!1,v=0,y=0,k=o.length;c<k;){var w=o[c++];if(64===w){r=o[c++];if(b||y)c+=r;else for(a=0;a<r;a++)f.push(o[c++])}else if(65===w){r=o[c++];if(b||y)c+=2*r;else for(a=0;a<r;a++){i=o[c++];f.push(i<<8|o[c++])}}else if(176==(248&w)){r=w-176+1;if(b||y)c+=r;else for(a=0;a<r;a++)f.push(o[c++])}else if(184==(248&w)){r=w-184+1;if(b||y)c+=2*r;else for(a=0;a<r;a++){i=o[c++];f.push(i<<8|o[c++])}}else if(43!==w||m)if(44!==w||m){if(45===w)if(b){b=!1;l=c}else{s=g.pop();if(!s){T("TT: ENDF bad stack");t.hintsValid=!1;return}n=p.pop();o=s.data;c=s.i;t.functionsStackDeltas[n]=f.length-s.stackTop}else if(137===w){if(b||y){T("TT: nested IDEFs not allowed");m=!0}b=!0;h=c}else if(88===w)++v;else if(27===w)y=v;else if(89===w){y===v&&(y=0);--v}else if(28===w&&!b&&!y){var C=f[f.length-1];C>0&&(c+=C-1)}}else{if(b||y){T("TT: nested FDEFs not allowed");m=!0}b=!0;h=c;n=f.pop();t.functionsDefined[n]={data:o,i:c}}else if(!b&&!y){n=f[f.length-1];t.functionsUsed[n]=!0;if(n in t.functionsStackDeltas)f.length+=t.functionsStackDeltas[n];else if(n in t.functionsDefined&&p.indexOf(n)<0){g.push({data:o,i:c,stackTop:f.length-1});p.push(n);s=t.functionsDefined[n];if(!s){T("TT: CALL non-existent function");t.hintsValid=!1;return}o=s.data;c=s.i}}if(!b&&!y){var x=w<=142?d[w]:w>=192&&w<=223?-1:w>=224?-2:0;if(w>=113&&w<=117){r=f.pop();isNaN(r)||(x=2*-r)}for(;x<0&&f.length>0;){f.pop();x++}for(;x>0;){f.push(NaN);x--}}}t.tooComplexToFollowFunctions=m;var S=[o];c>o.length&&S.push(new Uint8Array(c-o.length));if(h>l){T("TT: complementing a missing function tail");S.push(new Uint8Array([34,45]))}u(e,S)}function h(e,t){if(!e.tooComplexToFollowFunctions)if(e.functionsDefined.length>t){T("TT: more functions defined than expected");e.hintsValid=!1}else for(var a=0,r=e.functionsUsed.length;a<r;a++){if(a>t){T("TT: invalid function id: "+a);e.hintsValid=!1;return}if(e.functionsUsed[a]&&!e.functionsDefined[a]){T("TT: undefined function: "+a);e.hintsValid=!1;return}}}function u(e,t){if(t.length>1){var a,r,i=0;for(a=0,r=t.length;a<r;a++)i+=t[a].length;i=i+3&-4;var n=new Uint8Array(i),s=0;for(a=0,r=t.length;a<r;a++){n.set(t[a],s);s+=t[a].length}e.data=n;e.length=i}}function f(e,t,a){return!G[e]||(!!(!ee&&t>=0&&Q.has(t))||!!($&&a>=0&&A($[a])))}var d=[0,0,0,0,0,0,0,0,-2,-2,-2,-2,0,0,-2,-5,-1,-1,-1,-1,-1,-1,-1,-1,0,0,-1,0,-1,-1,-1,-1,1,-1,-999,0,1,0,-1,-2,0,-1,-2,-1,-1,0,-1,-1,0,0,-999,-999,-1,-1,-1,-1,-2,-999,-2,-2,-999,0,-2,-2,0,0,-2,0,-2,0,0,0,-2,-1,-1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,-1,0,-1,-1,0,-999,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,-2,-999,-999,-999,-999,-999,-1,-1,-2,-2,0,0,0,0,-1,-1,-999,-2,-2,0,0,-1,-2,-2,0,0,0,-1,-1,-1,-2];i=new M(new Uint8Array(i.getBytes()));var g,m,b=["OS/2","cmap","head","hhea","hmtx","maxp","name","post","loca","glyf","fpgm","prep","cvt ","CFF "],v=function(e){return{version:k(e.getBytes(4)),numTables:e.getUint16(),searchRange:e.getUint16(),entrySelector:e.getUint16(),rangeShift:e.getUint16()}}(i),I=v.numTables,B=Object.create(null);B["OS/2"]=null;B.cmap=null;B.head=null;B.hhea=null;B.hmtx=null;B.maxp=null;B.name=null;B.post=null;for(var R,L=0;L<I;L++){R=function(e){var t=k(e.getBytes(4)),a=e.getInt32()>>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.pos;e.pos=e.start?e.start:0;e.skip(r);var s=e.getBytes(i);e.pos=n;if("head"===t){s[8]=s[9]=s[10]=s[11]=0;s[17]|=32}return{tag:t,checksum:a,length:i,offset:r,data:s}}(i);b.indexOf(R.tag)<0||0!==R.length&&(B[R.tag]=R)}var D=!B["CFF "];if(D){B.loca||w('Required "loca" table is not found');if(!B.glyf){T('Required "glyf" table is not found -- trying to recover.');B.glyf={tag:"glyf",data:new Uint8Array(0)}}this.isOpenType=!1}else{if("OTTO"===v.version&&!n.composite||!B.head||!B.hhea||!B.maxp||!B.post){m=new M(B["CFF "].data);g=new ke(m,n);r(n);return this.convert(e,g,n)}delete B.glyf;delete B.loca;delete B.fpgm;delete B.prep;delete B["cvt "];this.isOpenType=!0}B.maxp||w('Required "maxp" table is not found');i.pos=(i.start||0)+B.maxp.offset;var U=i.getInt32(),N=i.getUint16(),_=0;if(U>=65536&&B.maxp.length>=22){i.pos+=8;if(i.getUint16()>2){B.maxp.data[14]=0;B.maxp.data[15]=2}i.pos+=4;_=i.getUint16()}var z=!1;if("CIDFontType2"===n.type&&n.toUnicode&&n.toUnicode.get(0)>"\0"){z=!0;N++;B.maxp.data[4]=N>>8;B.maxp.data[5]=255&N}var H=function(e,t,a,r){var i={functionsDefined:[],functionsUsed:[],functionsStackDeltas:[],tooComplexToFollowFunctions:!1,hintsValid:!0};e&&l(e,i);t&&l(t,i);e&&h(i,r);if(a&&1&a.length){var n=new Uint8Array(a.length+1);n.set(a.data);a.data=n}return i.hintsValid}(B.fpgm,B.prep,B["cvt "],_);if(!H){delete B.fpgm;delete B.prep;delete B["cvt "]}!function(e,t,a,r){if(t){e.pos=(e.start?e.start:0)+t.offset;e.pos+=t.length-2;var i=e.getUint16();if(i>r){C("The numOfMetrics ("+i+") should not be greater than the numGlyphs ("+r+")");i=r;t.data[34]=(65280&i)>>8;t.data[35]=255&i}var n=r-i,s=n-(a.length-4*i>>1);if(s>0){var o=new Uint8Array(a.length+2*s);o.set(a.data);a.data=o}}else a&&(a.data=null)}(i,B.hhea,B.hmtx,N);B.head||w('Required "head" table is not found');!function(e,a,r){var i=e.data,n=o(i[0],i[1],i[2],i[3]);if(n>>16!=1){C("Attempting to fix invalid version in head table: "+n);i[0]=0;i[1]=1;i[2]=0;i[3]=0}var s=t(i[50],i[51]);if(s<0||s>1){C("Attempting to fix invalid indexToLocFormat in head table: "+s);var c=a+1;if(r===c<<1){i[50]=0;i[51]=0}else if(r===c<<2){i[50]=0;i[51]=1}else T("Could not fix indexToLocFormat: "+s)}}(B.head,N,D?B.loca.length:0);var G=Object.create(null);if(D){var X=t(B.head.data[50],B.head.data[51]);G=function(e,t,a,r,i,n){var s,o,l;if(r){s=4;o=function(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]};l=function(e,t,a){e[t]=a>>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}}else{s=2;o=function(e,t){return e[t]<<9|e[t+1]<<1};l=function(e,t,a){e[t]=a>>9&255;e[t+1]=a>>1&255}}var h=e.data,u=s*(1+a);if(h.length!==u){h=new Uint8Array(u);h.set(e.data.subarray(0,u));e.data=h}var f=t.data,d=f.length,g=new Uint8Array(d),p=o(h,0),m=0,b=Object.create(null);l(h,0,m);var v,y;for(v=0,y=s;v<a;v++,y+=s){var k=o(h,y);k>d&&(d+3&-4)===k&&(k=d);if(k>d){l(h,y,m);p=k}else{p===k&&(b[v]=!0);m+=c(f,p,k,g,m,i);l(h,y,m);p=k}}if(0===m){var w=new Uint8Array([0,1,0,0,0,0,0,0,0,0,0,0,0,0,49,0]);for(v=0,y=s;v<a;v++,y+=s)l(h,y,w.length);t.data=w;return b}if(n){var C=o(h,s);if(g.length>C+m)t.data=g.subarray(0,C+m);else{t.data=new Uint8Array(C+m);t.data.set(g.subarray(0,m))}t.data.set(g.subarray(0,C),m);l(e.data,h.length-s,m+C)}else t.data=g.subarray(0,m);return b}(B.loca,B.glyf,N,X,H,z)}B.hhea||w('Required "hhea" table is not found');if(0===B.hhea.data[10]&&0===B.hhea.data[11]){B.hhea.data[10]=255;B.hhea.data[11]=255}var V={unitsPerEm:t(B.head.data[18],B.head.data[19]),yMax:t(B.head.data[42],B.head.data[43]),yMin:a(B.head.data[38],B.head.data[39]),ascent:t(B.hhea.data[4],B.hhea.data[5]),descent:a(B.hhea.data[6],B.hhea.data[7])};this.ascent=V.ascent/V.unitsPerEm;this.descent=V.descent/V.unitsPerEm;if(B.post){(function(e,t,a){var r=(i.start?i.start:0)+e.offset;i.pos=r;var n=e.length,s=r+n,o=i.getInt32();i.getBytes(28);var c,l,h=!0;switch(o){case 65536:c=ue;break;case 131072:var u=i.getUint16();if(u!==a){h=!1;break}var f=[];for(l=0;l<u;++l){var d=i.getUint16();if(d>=32768){h=!1;break}f.push(d)}if(!h)break;for(var g=[],p=[];i.pos<s;){var m=i.getByte();p.length=m;for(l=0;l<m;++l)p[l]=String.fromCharCode(i.getByte());g.push(p.join(""))}c=[];for(l=0;l<u;++l){var b=f[l];b<258?c.push(ue[b]):c.push(g[b-258])}break;case 196608:break;default:T("Unknown/unsupported post table version "+o);h=!1;t.defaultEncoding&&(c=t.defaultEncoding)}t.glyphNames=c;return h})(B.post,n,N)||(B.post=null)}var W,Y=[],Q=n.toUnicode,$=n.widths,ee=Q instanceof ge||65536===Q.length;if(n.composite){var te=n.cidToGidMap||[],ae=0===te.length;n.cMap.forEach(function(e,t){y(t<=65535,"Max size of CID is 65,535");var a=-1;ae?a=t:void 0!==te[t]&&(a=te[t]);a>=0&&a<N&&f(a,e,t)&&(Y[e]=a)});!z||!ae&&Y[0]||(Y[0]=N-1)}else{var re=function(e,t,a,r){if(!e){T("No cmap table available.");return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var i,n=(t.start?t.start:0)+e.offset;t.pos=n;t.getUint16();for(var s,o=t.getUint16(),c=!1,l=0;l<o;l++){var h=t.getUint16(),u=t.getUint16(),f=t.getInt32()>>>0,d=!1;if(0===h&&0===u)d=!0;else if(1===h&&0===u)d=!0;else if(3!==h||1!==u||!r&&s){if(a&&3===h&&0===u){d=!0;c=!0}}else{d=!0;a||(c=!0)}d&&(s={platformId:h,encodingId:u,offset:f});if(c)break}s&&(t.pos=n+s.offset);if(!s||-1===t.peekByte()){T("Could not find a preferred cmap table.");return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var g=t.getUint16();t.getUint16();t.getUint16();var p,m,b=!1,v=[];if(0===g){for(p=0;p<256;p++){var y=t.getByte();y&&v.push({charCode:p,glyphId:y})}b=!0}else if(4===g){var k=t.getUint16()>>1;t.getBytes(6);var w,C=[];for(w=0;w<k;w++)C.push({end:t.getUint16()});t.getUint16();for(w=0;w<k;w++)C[w].start=t.getUint16();for(w=0;w<k;w++)C[w].delta=t.getUint16();var x=0;for(w=0;w<k;w++){i=C[w];var S=t.getUint16();if(S){var A=(S>>1)-(k-w);i.offsetIndex=A;x=Math.max(x,A+i.end-i.start+1)}else i.offsetIndex=-1}var I=[];for(p=0;p<x;p++)I.push(t.getUint16());for(w=0;w<k;w++){i=C[w];n=i.start;var B=i.end,R=i.delta;A=i.offsetIndex;for(p=n;p<=B;p++)if(65535!==p){m=A<0?p:I[A+p-n];m=m+R&65535;v.push({charCode:p,glyphId:m})}}}else{if(6!==g){T("cmap table has unsupported format: "+g);return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var O=t.getUint16(),P=t.getUint16();for(p=0;p<P;p++){m=t.getUint16();var M=O+p;v.push({charCode:M,glyphId:m})}}v.sort(function(e,t){return e.charCode-t.charCode});for(l=1;l<v.length;l++)if(v[l-1].charCode===v[l].charCode){v.splice(l,1);l--}return{platformId:s.platformId,encodingId:s.encodingId,mappings:v,hasShortCmap:b}}(B.cmap,i,this.isSymbolicFont,n.hasEncoding),ie=re.platformId,ne=re.encodingId,se=re.mappings,oe=se.length;if(n.hasEncoding&&(3===ie&&1===ne||1===ie&&0===ne)||-1===ie&&-1===ne&&j(n.baseEncodingName)){var ce=[];"MacRomanEncoding"!==n.baseEncodingName&&"WinAnsiEncoding"!==n.baseEncodingName||(ce=j(n.baseEncodingName));var he=E();for(W=0;W<256;W++){var fe,de;fe=this.differences&&W in this.differences?this.differences[W]:W in ce&&""!==ce[W]?ce[W]:F[W];if(fe){de=s(fe,he);var me,be=!1;if(3===ie&&1===ne){me=he[de];be=!0}else 1===ie&&0===ne&&(me=q.indexOf(de));var ve=!1;for(L=0;L<oe;++L)if(se[L].charCode===me){var ye=be?W:me;if(f(se[L].glyphId,ye,-1)){Y[W]=se[L].glyphId;ve=!0;break}}if(!ve&&n.glyphNames){var we=n.glyphNames.indexOf(fe);-1===we&&de!==fe&&(we=n.glyphNames.indexOf(de));if(we>0&&f(we,-1,-1)){Y[W]=we;ve=!0}}ve||(Y[W]=0)}}}else if(0===ie&&0===ne)for(L=0;L<oe;++L)Y[se[L].charCode]=se[L].glyphId;else for(L=0;L<oe;++L){W=255&se[L].charCode;Y[W]=se[L].glyphId}}0===Y.length&&(Y[0]=0);var Ce=p(Y,n);this.toFontChar=Ce.toFontChar;B.cmap={tag:"cmap",data:x(Ce.charCodeToGlyphId,N)};B["OS/2"]&&S(B["OS/2"])||(B["OS/2"]={tag:"OS/2",data:O(n,Ce.charCodeToGlyphId,V)});B.post||(B.post={tag:"post",data:P(n)});if(!D)try{m=new M(B["CFF "].data);g=new J(m,n,le).parse();var xe=new Z(g);B["CFF "].data=xe.compile()}catch(e){T("Failed to compile font "+n.loadedName)}if(B.name){var Se=function(e){var t=(i.start?i.start:0)+e.offset;i.pos=t;var a=[[],[]],r=e.length,n=t+r;if(0!==i.getUint16()||r<6)return a;var s,o,c=i.getUint16(),l=i.getUint16(),h=[];for(s=0;s<c&&i.pos+12<=n;s++){var u={platform:i.getUint16(),encoding:i.getUint16(),language:i.getUint16(),name:i.getUint16(),length:i.getUint16(),offset:i.getUint16()};(1===u.platform&&0===u.encoding&&0===u.language||3===u.platform&&1===u.encoding&&1033===u.language)&&h.push(u)}for(s=0,o=h.length;s<o;s++){var f=h[s];if(!(f.length<=0)){var d=t+l+f.offset;if(!(d+f.length>n)){i.pos=d;var g=f.name;if(f.encoding){for(var p="",m=0,b=f.length;m<b;m+=2)p+=String.fromCharCode(i.getUint16());a[1][g]=p}else a[0][g]=k(i.getBytes(f.length))}}}return a}(B.name);B.name.data=K(e,Se)}else B.name={tag:"name",data:K(this.name)};var Ae=new pe(v.version);for(var Ie in B)Ae.addTable(Ie,B[Ie].data);return Ae.toArray()},convert:function(e,t,a){function r(e,t){for(var a in e)if(t===e[a])return 0|a;s.charCodeToGlyphId[s.nextAvailableFontCharCode]=t;return s.nextAvailableFontCharCode++}a.fixedPitch=!1;a.builtInEncoding&&i(a,a.builtInEncoding);var n=t.getGlyphMapping(a),s=p(n,a);this.toFontChar=s.toFontChar;var o=t.numGlyphs,h=t.seacs;if(le&&h&&h.length){var u=a.fontMatrix||b,f=t.getCharset(),d=Object.create(null);for(var g in h){g|=0;var m=h[g],v=F[m[2]],y=F[m[3]],k=f.indexOf(v),w=f.indexOf(y);if(!(k<0||w<0)){var C={x:m[0]*u[0]+m[1]*u[2]+u[4],y:m[0]*u[1]+m[1]*u[3]+u[5]},S=function(e,t){var a=null;for(var r in e)if(t===e[r]){a||(a=[]);a.push(0|r)}return a}(n,g);if(S)for(var A=0,I=S.length;A<I;A++){var B=S[A],R=s.charCodeToGlyphId,T=r(R,k),M=r(R,w);d[B]={ +baseFontCharCode:T,accentFontCharCode:M,accentOffset:C}}}}a.seacMap=d}var E=1/(a.fontMatrix||b)[0],L=new pe("OTTO");L.addTable("CFF ",t.data);L.addTable("OS/2",O(a,s.charCodeToGlyphId));L.addTable("cmap",x(s.charCodeToGlyphId,o));L.addTable("head","\0\0\0\0\0\0\0\0\0\0_<õ\0\0"+l(E)+"\0\0\0\0\v~'\0\0\0\0\v~'\0\0"+l(a.descent)+"ÿ"+l(a.ascent)+c(a.italicAngle?2:0)+"\0\0\0\0\0\0\0");L.addTable("hhea","\0\0\0"+l(a.ascent)+l(a.descent)+"\0\0ÿÿ\0\0\0\0\0\0"+l(a.capHeight)+l(Math.tan(a.italicAngle)*a.xHeight)+"\0\0\0\0\0\0\0\0\0\0\0\0"+c(o));L.addTable("hmtx",function(){for(var e=t.charstrings,a=t.cff?t.cff.widths:null,r="\0\0\0\0",i=1,n=o;i<n;i++){var s=0;if(e){var l=e[i-1];s="width"in l?l.width:0}else a&&(s=Math.ceil(a[i]||0));r+=c(s)+c(0)}return r}());L.addTable("maxp","\0\0P\0"+c(o));L.addTable("name",K(e));L.addTable("post",P(a));return L.toArray()},get spaceWidth(){if("_shadowWidth"in this)return this._shadowWidth;for(var e,t=["space","minus","one","i","I"],a=0,r=t.length;a<r;a++){var i=t[a];if(i in this.widths){e=this.widths[i];break}var n=E(),s=n[i],o=0;this.composite&&this.cMap.contains(s)&&(o=this.cMap.lookup(s));!o&&this.toUnicode&&(o=this.toUnicode.charCodeOf(s));o<=0&&(o=s);e=this.widths[o];if(e)break}e=e||this.defaultWidth;this._shadowWidth=e;return e},charToGlyph:function(e,t){var a,r,i,n=e;this.cMap&&this.cMap.contains(e)&&(n=this.cMap.lookup(e));r=this.widths[n];r=A(r)?r:this.defaultWidth;var s=this.vmetrics&&this.vmetrics[n],o=this.toUnicode.get(e)||e;"number"==typeof o&&(o=String.fromCharCode(o));var c=e in this.toFontChar;a=this.toFontChar[e]||e;this.missingFile&&(a=V(a));this.isType3Font&&(i=a);var l=null;if(this.seacMap&&this.seacMap[e]){c=!0;var h=this.seacMap[e];a=h.baseFontCharCode;l={fontChar:String.fromCharCode(h.accentFontCharCode),offset:h.accentOffset}}var u=String.fromCharCode(a),f=this.glyphCache[e];if(!f||!f.matchesForCache(u,o,l,r,s,i,t,c)){f=new fe(u,o,l,r,s,i,t,c);this.glyphCache[e]=f}return f},charsToGlyphs:function(e){var t,a,r,i=this.charsCache;if(i){t=i[e];if(t)return t}i||(i=this.charsCache=Object.create(null));t=[];var n,s=e,o=0;if(this.cMap)for(var c=Object.create(null);o<e.length;){this.cMap.readCharCode(e,o,c);r=c.charcode;var l=c.length;o+=l;var h=1===l&&32===e.charCodeAt(o-1);a=this.charToGlyph(r,h);t.push(a)}else for(o=0,n=e.length;o<n;++o){r=e.charCodeAt(o);a=this.charToGlyph(r,32===r);t.push(a)}return i[s]=t}};return e}(),ve=function(){function e(e){this.error=e;this.loadedName="g_font_error";this.loading=!1}e.prototype={charsToGlyphs:function(){return[]},exportData:function(){return{error:this.error}}};return e}(),ye=function(){function e(e,t,a){for(var r,i=e.length,n=t.length,s=i-n,o=a,c=!1;o<s;){r=0;for(;r<n&&e[o+r]===t[r];)r++;if(r>=n){o+=r;for(;o<i&&P(e[o]);)o++;c=!0;break}o++}return{found:c,length:o}}function t(t,a){var r,i,n,s=[101,101,120,101,99],o=t.pos;try{r=t.getBytes(a);i=r.length}catch(e){if(e instanceof O)throw e}if(i===a){n=e(r,s,a-2*s.length);if(n.found&&n.length===a)return{stream:new M(r),length:a}}T('Invalid "Length1" property in Type1 font -- trying to recover.');t.pos=o;for(var c;;){n=e(t.peekBytes(2048),s,0);if(0===n.length)break;t.pos+=n.length;if(n.found){c=t.pos-o;break}}t.pos=o;if(c)return{stream:new M(t.getBytes(c)),length:c};T('Unable to recover "Length1" property in Type1 font -- using as is.');return{stream:new M(t.getBytes(a)),length:a}}function a(e,t){var a=e.getBytes();return{stream:new M(a),length:a.length}}function r(e,r,i){var n=i.length1,s=i.length2,o=r.peekBytes(6),c=128===o[0]&&1===o[1];if(c){r.skip(6);n=o[5]<<24|o[4]<<16|o[3]<<8|o[2]}var l=t(r,n);n=l.length;new K(l.stream,!1,le).extractFontHeader(i);if(c){o=r.getBytes(6);s=o[5]<<24|o[4]<<16|o[3]<<8|o[2]}var h=a(r,s);s=h.length;var u=new K(h.stream,!0,le),f=u.extractFontProgram();for(var d in f.properties)i[d]=f.properties[d];var g=f.charstrings,p=this.getType2Charstrings(g),m=this.getType2Subrs(f.subrs);this.charstrings=g;this.data=this.wrap(e,p,this.charstrings,m,i);this.seacs=this.getSeacs(f.charstrings)}r.prototype={get numGlyphs(){return this.charstrings.length+1},getCharset:function(){for(var e=[".notdef"],t=this.charstrings,a=0;a<t.length;a++)e.push(t[a].glyphName);return e},getGlyphMapping:function(e){var t,a=this.charstrings,r=[".notdef"];for(t=0;t<a.length;t++)r.push(a[t].glyphName);var i=e.builtInEncoding;if(i){var n=Object.create(null);for(var s in i){t=r.indexOf(i[s]);t>=0&&(n[s]=t)}}return o(e,n,r)},getSeacs:function(e){var t,a,r=[];for(t=0,a=e.length;t<a;t++){var i=e[t];i.seac&&(r[t+1]=i.seac)}return r},getType2Charstrings:function(e){for(var t=[],a=0,r=e.length;a<r;a++)t.push(e[a].charstring);return t},getType2Subrs:function(e){var t=0,a=e.length;t=a<1133?107:a<33769?1131:32768;var r,i=[];for(r=0;r<t;r++)i.push([11]);for(r=0;r<a;r++)i.push(e[r]);return i},wrap:function(e,t,a,r,i){var n=new Q;n.header=new $(1,0,4,4);n.names=[e];var s=new ee;s.setByName("version",391);s.setByName("Notice",392);s.setByName("FullName",393);s.setByName("FamilyName",394);s.setByName("Weight",395);s.setByName("Encoding",null);s.setByName("FontMatrix",i.fontMatrix);s.setByName("FontBBox",i.bbox);s.setByName("charset",null);s.setByName("CharStrings",null);s.setByName("Private",null);n.topDict=s;var o=new ae;o.add("Version 0.11");o.add("See original notice");o.add(e);o.add(e);o.add("Medium");n.strings=o;n.globalSubrIndex=new re;var c,l,h=t.length,u=[0];for(c=0;c<h;c++){var f=Y.indexOf(a[c].glyphName);-1===f&&(f=0);u.push(f>>8&255,255&f)}n.charset=new ie(!1,0,[],u);var d=new re;d.add([139,14]);for(c=0;c<h;c++){var g=t[c];0!==g.length?d.add(g):d.add([139,14])}n.charStrings=d;var p=new te;p.setByName("Subrs",null);var m=["BlueValues","OtherBlues","FamilyBlues","FamilyOtherBlues","StemSnapH","StemSnapV","BlueShift","BlueFuzz","BlueScale","LanguageGroup","ExpansionFactor","ForceBold","StdHW","StdVW"];for(c=0,l=m.length;c<l;c++){var b=m[c];if(b in i.privateData){var v=i.privateData[b];if(x(v))for(var y=v.length-1;y>0;y--)v[y]-=v[y-1];p.setByName(b,v)}}n.topDict.privateDict=p;var k=new re;for(c=0,l=r.length;c<l;c++)k.add(r[c]);p.subrsIndex=k;return new Z(n).compile()}};return r}(),ke=function(){function e(e,t){this.properties=t;var a=new J(e,t,le);this.cff=a.parse();var r=new Z(this.cff);this.seacs=this.cff.seacs;try{this.data=r.compile()}catch(a){T("Failed to compile font "+t.loadedName);this.data=e}}e.prototype={get numGlyphs(){return this.cff.charStrings.count},getCharset:function(){return this.cff.charset.charset},getGlyphMapping:function(){var e,t,a=this.cff,r=this.properties,i=a.charset.charset;if(r.composite){e=Object.create(null);if(a.isCIDFont)for(t=0;t<i.length;t++){var n=i[t],s=r.cMap.charCodeOf(n);e[s]=t}else for(t=0;t<a.charStrings.count;t++)e[t]=t;return e}e=o(r,a.encoding?a.encoding.encoding:null,i);return e}};return e}();!function(){"undefined"!=typeof navigator&&/Windows/.test(navigator.userAgent)&&(le=!0)}();!function(){"undefined"!=typeof navigator&&/Windows.*Chrome/.test(navigator.userAgent)&&(oe=!0)}();t.SEAC_ANALYSIS_ENABLED=le;t.PRIVATE_USE_OFFSET_START=ne;t.PRIVATE_USE_OFFSET_END=se;t.ErrorFont=ve;t.Font=be;t.FontFlags=he;t.IdentityToUnicodeMap=ge;t.ProblematicCharRanges=me;t.ToUnicodeMap=de;t.getFontType=n},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(3),s=a(2),o=a(15),c=r.ImageKind,l=r.assert,h=r.error,u=r.info,f=r.isArray,d=r.warn,g=i.Name,p=i.isStream,m=n.ColorSpace,b=s.DecodeStream,v=s.JpegStream,y=o.JpxImage,k=function(){function e(e,t){return t&&t.canDecode(e)?t.decode(e):Promise.resolve(e)}function t(e,t,a,r){e=t+e*a;return e<0?0:e>r?r:e}function a(e,t,a,r,i,n){var s,o,c,l,h=i*n,u=t<=8?new Uint8Array(h):t<=16?new Uint16Array(h):new Uint32Array(h),f=a/i,d=r/n,g=0,p=new Uint16Array(i),m=a;for(s=0;s<i;s++)p[s]=Math.floor(s*f);for(s=0;s<n;s++){c=Math.floor(s*d)*m;for(o=0;o<i;o++){l=c+p[o];u[g++]=e[l]}}return u}function r(e,t,a,i,n,s,o){this.image=a;var c=a.dict;if(c.has("Filter")){var l=c.get("Filter").name;if("JPXDecode"===l){var f=new y;f.parseImageProperties(a.stream);a.stream.reset();a.bitsPerComponent=f.bitsPerComponent;a.numComps=f.componentsCount}else if("JBIG2Decode"===l){a.bitsPerComponent=1;a.numComps=1}}this.width=c.get("Width","W");this.height=c.get("Height","H");(this.width<1||this.height<1)&&h("Invalid image width: "+this.width+" or height: "+this.height);this.interpolate=c.get("Interpolate","I")||!1;this.imageMask=c.get("ImageMask","IM")||!1;this.matte=c.get("Matte")||!1;var b=a.bitsPerComponent;if(!b){b=c.get("BitsPerComponent","BPC");b||(this.imageMask?b=1:h("Bits per component missing in image: "+this.imageMask))}this.bpc=b;if(!this.imageMask){var v=c.get("ColorSpace","CS");if(!v){u("JPX images (which do not require color spaces)");switch(a.numComps){case 1:v=g.get("DeviceGray");break;case 3:v=g.get("DeviceRGB");break;case 4:v=g.get("DeviceCMYK");break;default:h("JPX images with "+this.numComps+" color components not supported.")}}this.colorSpace=m.parse(v,e,t);this.numComps=this.colorSpace.numComps}this.decode=c.getArray("Decode","D");this.needsDecode=!1;if(this.decode&&(this.colorSpace&&!this.colorSpace.isDefaultDecode(this.decode)||o&&!m.isDefaultDecode(this.decode,1))){this.needsDecode=!0;var k=(1<<b)-1;this.decodeCoefficients=[];this.decodeAddends=[];for(var w=0,C=0;w<this.decode.length;w+=2,++C){var x=this.decode[w],S=this.decode[w+1];this.decodeCoefficients[C]=S-x;this.decodeAddends[C]=k*x}}if(n)this.smask=new r(e,t,n,!1);else if(s)if(p(s)){var A=s.dict,I=A.get("ImageMask","IM");I?this.mask=new r(e,t,s,!1,null,null,!0):d("Ignoring /Mask in image without /ImageMask.")}else this.mask=s}r.buildImage=function(t,a,i,n,s,o){var c,l,h=e(n,o),u=n.dict.get("SMask"),g=n.dict.get("Mask");if(u){c=e(u,o);l=Promise.resolve(null)}else{c=Promise.resolve(null);if(g)if(p(g))l=e(g,o);else if(f(g))l=Promise.resolve(g);else{d("Unsupported mask format.");l=Promise.resolve(null)}else l=Promise.resolve(null)}return Promise.all([h,c,l]).then(function(e){var t=e[0],n=e[1],o=e[2];return new r(a,i,t,s,n,o)})};r.createMask=function(e,t,a,r,i){var n,s,o=(t+7>>3)*a,c=e.byteLength,l=o===c;if(!r||i&&!l)if(i){n=new Uint8Array(o);n.set(e);for(s=c;s<o;s++)n[s]=255}else{n=new Uint8Array(c);n.set(e)}else n=e;if(i)for(s=0;s<c;s++)n[s]=~n[s];return{data:n,width:t,height:a}};r.prototype={get drawWidth(){return Math.max(this.width,this.smask&&this.smask.width||0,this.mask&&this.mask.width||0)},get drawHeight(){return Math.max(this.height,this.smask&&this.smask.height||0,this.mask&&this.mask.height||0)},decodeBuffer:function(e){var a,r,i=this.bpc,n=this.numComps,s=this.decodeAddends,o=this.decodeCoefficients,c=(1<<i)-1;if(1!==i){var l=0;for(a=0,r=this.width*this.height;a<r;a++)for(var h=0;h<n;h++){e[l]=t(e[l],s[h],o[h],c);l++}}else for(a=0,r=e.length;a<r;a++)e[a]=+!e[a]},getComponents:function(e){var t=this.bpc;if(8===t)return e;var a,r,i=this.width,n=this.height,s=this.numComps,o=i*n*s,c=0,l=t<=8?new Uint8Array(o):t<=16?new Uint16Array(o):new Uint32Array(o),h=i*s,u=(1<<t)-1,f=0;if(1===t)for(var d,g,p,m=0;m<n;m++){g=f+(-8&h);p=f+h;for(;f<g;){r=e[c++];l[f]=r>>7&1;l[f+1]=r>>6&1;l[f+2]=r>>5&1;l[f+3]=r>>4&1;l[f+4]=r>>3&1;l[f+5]=r>>2&1;l[f+6]=r>>1&1;l[f+7]=1&r;f+=8}if(f<p){r=e[c++];d=128;for(;f<p;){l[f++]=+!!(r&d);d>>=1}}}else{var b=0;r=0;for(f=0,a=o;f<a;++f){if(f%h==0){r=0;b=0}for(;b<t;){r=r<<8|e[c++];b+=8}var v=b-t,y=r>>v;l[f]=y<0?0:y>u?u:y;r&=(1<<v)-1;b=v}}return l},fillOpacity:function(e,t,i,n,s){var o,c,l,u,d,g,p=this.smask,m=this.mask;if(p){c=p.width;l=p.height;o=new Uint8Array(c*l);p.fillGrayBuffer(o);c===t&&l===i||(o=a(o,p.bpc,c,l,t,i))}else if(m)if(m instanceof r){c=m.width;l=m.height;o=new Uint8Array(c*l);m.numComps=1;m.fillGrayBuffer(o);for(u=0,d=c*l;u<d;++u)o[u]=255-o[u];c===t&&l===i||(o=a(o,m.bpc,c,l,t,i))}else if(f(m)){o=new Uint8Array(t*i);var b=this.numComps;for(u=0,d=t*i;u<d;++u){var v=0,y=u*b;for(g=0;g<b;++g){var k=s[y+g],w=2*g;if(k<m[w]||k>m[w+1]){v=255;break}}o[u]=v}}else h("Unknown mask format.");if(o)for(u=0,g=3,d=t*n;u<d;++u,g+=4)e[g]=o[u];else for(u=0,g=3,d=t*n;u<d;++u,g+=4)e[g]=255},undoPreblend:function(e,t,a){var r=this.smask&&this.smask.matte;if(r)for(var i,n,s,o=this.colorSpace.getRgb(r,0),c=o[0],l=o[1],h=o[2],u=t*a*4,f=0;f<u;f+=4){var d=e[f+3];if(0!==d){var g=255/d;i=(e[f]-c)*g+c;n=(e[f+1]-l)*g+l;s=(e[f+2]-h)*g+h;e[f]=i<=0?0:i>=255?255:0|i;e[f+1]=n<=0?0:n>=255?255:0|n;e[f+2]=s<=0?0:s>=255?255:0|s}else{e[f]=255;e[f+1]=255;e[f+2]=255}}},createImageData:function(e){var t,a=this.drawWidth,r=this.drawHeight,i={width:a,height:r},n=this.numComps,s=this.width,o=this.height,h=this.bpc,u=s*n*h+7>>3;if(!e){var f;"DeviceGray"===this.colorSpace.name&&1===h?f=c.GRAYSCALE_1BPP:"DeviceRGB"!==this.colorSpace.name||8!==h||this.needsDecode||(f=c.RGB_24BPP);if(f&&!this.smask&&!this.mask&&a===s&&r===o){i.kind=f;t=this.getImageBytes(o*u);if(this.image instanceof b)i.data=t;else{var d=new Uint8Array(t.length);d.set(t);i.data=d}if(this.needsDecode){l(f===c.GRAYSCALE_1BPP);for(var g=i.data,p=0,m=g.length;p<m;p++)g[p]^=255}return i}if(this.image instanceof v&&!this.smask&&!this.mask&&("DeviceGray"===this.colorSpace.name||"DeviceRGB"===this.colorSpace.name||"DeviceCMYK"===this.colorSpace.name)){i.kind=c.RGB_24BPP;i.data=this.getImageBytes(o*u,a,r,!0);return i}}t=this.getImageBytes(o*u);var y,k,w=0|t.length/u*r/o,C=this.getComponents(t);if(e||this.smask||this.mask){i.kind=c.RGBA_32BPP;i.data=new Uint8Array(a*r*4);y=1;k=!0;this.fillOpacity(i.data,a,r,w,C)}else{i.kind=c.RGB_24BPP;i.data=new Uint8Array(a*r*3);y=0;k=!1}this.needsDecode&&this.decodeBuffer(C);this.colorSpace.fillRgb(i.data,s,o,a,r,w,h,C,y);k&&this.undoPreblend(i.data,a,w);return i},fillGrayBuffer:function(e){var t=this.numComps;1!==t&&h("Reading gray scale from a color image: "+t);var a,r,i=this.width,n=this.height,s=this.bpc,o=i*t*s+7>>3,c=this.getImageBytes(n*o),l=this.getComponents(c);if(1!==s){this.needsDecode&&this.decodeBuffer(l);r=i*n;var u=255/((1<<s)-1);for(a=0;a<r;++a)e[a]=u*l[a]|0}else{r=i*n;if(this.needsDecode)for(a=0;a<r;++a)e[a]=l[a]-1&255;else for(a=0;a<r;++a)e[a]=255&-l[a]}},getImageBytes:function(e,t,a,r){this.image.reset();this.image.drawWidth=t||this.width;this.image.drawHeight=a||this.height;this.image.forceRGB=!!r;return this.image.getBytes(e)}};return r}();t.PDFImage=k},function(e,t,a){"use strict";var r=a(0),i=a(10),n=r.error,s=r.log2,o=r.readInt8,c=r.readUint16,l=r.readUint32,h=r.shadow,u=i.ArithmeticDecoder,f=function(){function e(){}function t(e,t,a){this.data=e;this.start=t;this.end=a}function a(e,t,a){function r(e){for(var t=0,r=0;r<e;r++){var s=a.readBit(i,n);n=n<256?n<<1|s:511&(n<<1|s)|256;t=t<<1|s}return t>>>0}var i=e.getContexts(t),n=1,s=r(1),o=r(1)?r(1)?r(1)?r(1)?r(1)?r(32)+4436:r(12)+340:r(8)+84:r(6)+20:r(4)+4:r(2);return 0===s?o:o>0?-o:null}function r(e,t,a){for(var r=e.getContexts("IAID"),i=1,n=0;n<a;n++){i=i<<1|t.readBit(r,i)}return a<31?i&(1<<a)-1:2147483647&i}function i(e,t,a){var r,i,n,s,o,c,l,h=a.decoder,u=a.contextCache.getContexts("GB"),f=[];for(i=0;i<t;i++){o=f[i]=new Uint8Array(e);c=i<1?o:f[i-1];l=i<2?o:f[i-2];r=l[0]<<13|l[1]<<12|l[2]<<11|c[0]<<7|c[1]<<6|c[2]<<5|c[3]<<4;for(n=0;n<e;n++){o[n]=s=h.readBit(u,r);r=(31735&r)<<1|(n+3<e?l[n+3]<<11:0)|(n+4<e?c[n+4]<<4:0)|s}}return f}function f(e,t,a,r,s,o,c,l){e&&n("JBIG2 error: MMR encoding is not supported");if(0===r&&!o&&!s&&4===c.length&&3===c[0].x&&-1===c[0].y&&-3===c[1].x&&-1===c[1].y&&2===c[2].x&&-2===c[2].y&&-2===c[3].x&&-2===c[3].y)return i(t,a,l);var h=!!o,u=A[r].concat(c);u.sort(function(e,t){return e.y-t.y||e.x-t.x});var f,d,g=u.length,p=new Int8Array(g),m=new Int8Array(g),b=[],v=0,y=0,k=0,w=0;for(d=0;d<g;d++){p[d]=u[d].x;m[d]=u[d].y;y=Math.min(y,u[d].x);k=Math.max(k,u[d].x);w=Math.min(w,u[d].y);d<g-1&&u[d].y===u[d+1].y&&u[d].x===u[d+1].x-1?v|=1<<g-1-d:b.push(d)}var C=b.length,x=new Int8Array(C),S=new Int8Array(C),I=new Uint16Array(C);for(f=0;f<C;f++){d=b[f];x[f]=u[d].x;S[f]=u[d].y;I[f]=1<<g-1-d}for(var R,T,O,P,M,E=-y,L=-w,D=t-k,F=B[r],q=new Uint8Array(t),U=[],N=l.decoder,j=l.contextCache.getContexts("GB"),_=0,z=0,H=0;H<a;H++){if(s){_^=N.readBit(j,F);if(_){U.push(q);continue}}q=new Uint8Array(q);U.push(q);for(R=0;R<t;R++)if(h&&o[H][R])q[R]=0;else{if(R>=E&&R<D&&H>=L){z=z<<1&v;for(d=0;d<C;d++){T=H+S[d];O=R+x[d];P=U[T][O];if(P){P=I[d];z|=P}}}else{z=0;M=g-1;for(d=0;d<g;d++,M--){O=R+p[d];if(O>=0&&O<t){T=H+m[d];if(T>=0){P=U[T][O];P&&(z|=P<<M)}}}}var G=N.readBit(j,z);q[R]=G}}return U}function d(e,t,a,r,i,s,o,c,l){var h=I[a].coding;0===a&&(h=h.concat([c[0]]));var u,f=h.length,d=new Int32Array(f),g=new Int32Array(f);for(u=0;u<f;u++){d[u]=h[u].x;g[u]=h[u].y}var p=I[a].reference;0===a&&(p=p.concat([c[1]]));var m=p.length,b=new Int32Array(m),v=new Int32Array(m);for(u=0;u<m;u++){b[u]=p[u].x;v[u]=p[u].y}for(var y=r[0].length,k=r.length,w=R[a],C=[],x=l.decoder,S=l.contextCache.getContexts("GR"),A=0,B=0;B<t;B++){if(o){A^=x.readBit(S,w);A&&n("JBIG2 error: prediction is not supported")}var T=new Uint8Array(e);C.push(T);for(var O=0;O<e;O++){var P,M,E=0;for(u=0;u<f;u++){P=B+g[u];M=O+d[u];P<0||M<0||M>=e?E<<=1:E=E<<1|C[P][M]}for(u=0;u<m;u++){P=B+v[u]+s;M=O+b[u]+i;P<0||P>=k||M<0||M>=y?E<<=1:E=E<<1|r[P][M]}var L=x.readBit(S,E);T[O]=L}}return C}function g(e,t,i,o,c,l,h,u,g,m,b){e&&n("JBIG2 error: huffman is not supported");for(var v=[],y=0,k=s(i.length+o),w=b.decoder,C=b.contextCache;v.length<o;){y+=a(C,"IADH",w);for(var x=0;;){var S=a(C,"IADW",w);if(null===S)break;x+=S;var A;if(t){var I=a(C,"IAAI",w);if(I>1)A=p(e,t,x,y,0,I,1,i.concat(v),k,0,0,1,0,l,g,m,b);else{var B=r(C,w,k),R=a(C,"IARDX",w),T=a(C,"IARDY",w);A=d(x,y,g,B<i.length?i[B]:v[B-i.length],R,T,!1,m,b)}}else A=f(!1,x,y,h,!1,null,u,b);v.push(A)}}for(var O=[],P=[],M=!1,E=i.length+o;P.length<E;){for(var L=a(C,"IAEX",w);L--;)P.push(M);M=!M}for(var D=0,F=i.length;D<F;D++)P[D]&&O.push(i[D]);for(var q=0;q<o;D++,q++)P[D]&&O.push(v[q]);return O}function p(e,t,i,s,o,c,l,h,u,f,g,p,m,b,v,y,k){e&&n("JBIG2 error: huffman is not supported");var w,C,x=[];for(w=0;w<s;w++){C=new Uint8Array(i);if(o)for(var S=0;S<i;S++)C[S]=o;x.push(C)}var A=k.decoder,I=k.contextCache,B=-a(I,"IADT",A),R=0;w=0;for(;w<c;){B+=a(I,"IADT",A);R+=a(I,"IAFS",A);for(var T=R;;){var O=1===l?0:a(I,"IAIT",A),P=l*B+O,M=r(I,A,u),E=t&&a(I,"IARI",A),L=h[M],D=L[0].length,F=L.length;if(E){var q=a(I,"IARDW",A),U=a(I,"IARDH",A),N=a(I,"IARDX",A),j=a(I,"IARDY",A);D+=q;F+=U;L=d(D,F,v,L,(q>>1)+N,(U>>1)+j,!1,y,k)}var _,z,H,G=P-(1&p?0:F),X=T-(2&p?D:0);if(f){for(_=0;_<F;_++){C=x[X+_];if(C){H=L[_];var V=Math.min(i-G,D);switch(m){case 0:for(z=0;z<V;z++)C[G+z]|=H[z];break;case 2:for(z=0;z<V;z++)C[G+z]^=H[z];break;default:n("JBIG2 error: operator "+m+" is not supported")}}}T+=F-1}else{for(z=0;z<F;z++){C=x[G+z];if(C){H=L[z];switch(m){case 0:for(_=0;_<D;_++)C[X+_]|=H[_];break;case 2:for(_=0;_<D;_++)C[X+_]^=H[_];break;default:n("JBIG2 error: operator "+m+" is not supported")}}}T+=D-1}w++;var W=a(I,"IADS",A);if(null===W)break;T+=W+g}}return x}function m(e,t){var a={};a.number=l(e,t);var r=e[t+4],i=63&r;S[i]||n("JBIG2 error: invalid segment type: "+i);a.type=i;a.typeName=S[i];a.deferredNonRetain=!!(128&r);var s=!!(64&r),o=e[t+5],h=o>>5&7,u=[31&o],f=t+6;if(7===o){h=536870911&l(e,f-1);f+=3;var d=h+7>>3;u[0]=e[f++];for(;--d>0;)u.push(e[f++])}else 5!==o&&6!==o||n("JBIG2 error: invalid referred-to flags");a.retainBits=u;var g,p,m=a.number<=256?1:a.number<=65536?2:4,b=[];for(g=0;g<h;g++){var y=1===m?e[f]:2===m?c(e,f):l(e,f);b.push(y);f+=m}a.referredTo=b;if(s){a.pageAssociation=l(e,f);f+=4}else a.pageAssociation=e[f++];a.length=l(e,f);f+=4;if(4294967295===a.length)if(38===i){var k=v(e,f),w=e[f+T],C=!!(1&w),x=new Uint8Array(6);if(!C){x[0]=255;x[1]=172}x[2]=k.height>>>24&255;x[3]=k.height>>16&255;x[4]=k.height>>8&255;x[5]=255&k.height;for(g=f,p=e.length;g<p;g++){for(var A=0;A<6&&x[A]===e[g+A];)A++;if(6===A){a.length=g+6;break}}4294967295===a.length&&n("JBIG2 error: segment end was not found")}else n("JBIG2 error: invalid unknown segment length");a.headerEnd=f;return a}function b(e,t,a,r){for(var i=[],n=a;n<r;){var s=m(t,n);n=s.headerEnd;var o={header:s,data:t};if(!e.randomAccess){o.start=n;n+=s.length;o.end=n}i.push(o);if(51===s.type)break}if(e.randomAccess)for(var c=0,l=i.length;c<l;c++){i[c].start=n;n+=i[c].header.length;i[c].end=n}return i}function v(e,t){return{width:l(e,t),height:l(e,t+4),x:l(e,t+8),y:l(e,t+12),combinationOperator:7&e[t+16]}}function y(e,t){var a,r,i,s,h=e.header,u=e.data,f=e.start,d=e.end;switch(h.type){case 0:var g={},p=c(u,f);g.huffman=!!(1&p);g.refinement=!!(2&p);g.huffmanDHSelector=p>>2&3;g.huffmanDWSelector=p>>4&3;g.bitmapSizeSelector=p>>6&1;g.aggregationInstancesSelector=p>>7&1;g.bitmapCodingContextUsed=!!(256&p);g.bitmapCodingContextRetained=!!(512&p);g.template=p>>10&3;g.refinementTemplate=p>>12&1;f+=2;if(!g.huffman){s=0===g.template?4:1;r=[];for(i=0;i<s;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}g.at=r}if(g.refinement&&!g.refinementTemplate){r=[];for(i=0;i<2;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}g.refinementAt=r}g.numberOfExportedSymbols=l(u,f);f+=4;g.numberOfNewSymbols=l(u,f);f+=4;a=[g,h.number,h.referredTo,u,f,d];break;case 6:case 7:var m={};m.info=v(u,f);f+=T;var b=c(u,f);f+=2;m.huffman=!!(1&b);m.refinement=!!(2&b);m.stripSize=1<<(b>>2&3);m.referenceCorner=b>>4&3;m.transposed=!!(64&b);m.combinationOperator=b>>7&3;m.defaultPixelValue=b>>9&1;m.dsOffset=b<<17>>27;m.refinementTemplate=b>>15&1;if(m.huffman){var y=c(u,f);f+=2;m.huffmanFS=3&y;m.huffmanDS=y>>2&3;m.huffmanDT=y>>4&3;m.huffmanRefinementDW=y>>6&3;m.huffmanRefinementDH=y>>8&3;m.huffmanRefinementDX=y>>10&3;m.huffmanRefinementDY=y>>12&3;m.huffmanRefinementSizeSelector=!!(14&y)}if(m.refinement&&!m.refinementTemplate){r=[];for(i=0;i<2;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}m.refinementAt=r}m.numberOfSymbolInstances=l(u,f);f+=4;m.huffman&&n("JBIG2 error: huffman is not supported");a=[m,h.referredTo,u,f,d];break;case 38:case 39:var k={};k.info=v(u,f);f+=T;var w=u[f++];k.mmr=!!(1&w);k.template=w>>1&3;k.prediction=!!(8&w);if(!k.mmr){s=0===k.template?4:1;r=[];for(i=0;i<s;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}k.at=r}a=[k,u,f,d];break;case 48:var C={width:l(u,f),height:l(u,f+4),resolutionX:l(u,f+8),resolutionY:l(u,f+12)};4294967295===C.height&&delete C.height;var x=u[f+16];c(u,f+17);C.lossless=!!(1&x);C.refinement=!!(2&x);C.defaultPixelValue=x>>2&1;C.combinationOperator=x>>3&3;C.requiresBuffer=!!(32&x);C.combinationOperatorOverride=!!(64&x);a=[C];break;case 49:case 50:case 51:case 62:break;default:n("JBIG2 error: segment type "+h.typeName+"("+h.type+") is not implemented")}var S="on"+h.typeName;S in t&&t[S].apply(t,a)}function k(e,t){for(var a=0,r=e.length;a<r;a++)y(e[a],t)}function w(e){for(var t=new C,a=0,r=e.length;a<r;a++){var i=e[a];k(b({},i.data,i.start,i.end),t)}return t.buffer}function C(){}function x(){}e.prototype={getContexts:function(e){return e in this?this[e]:this[e]=new Int8Array(65536)}};t.prototype={get decoder(){var e=new u(this.data,this.start,this.end);return h(this,"decoder",e)},get contextCache(){var t=new e;return h(this,"contextCache",t)}};var S=["SymbolDictionary",null,null,null,"IntermediateTextRegion",null,"ImmediateTextRegion","ImmediateLosslessTextRegion",null,null,null,null,null,null,null,null,"patternDictionary",null,null,null,"IntermediateHalftoneRegion",null,"ImmediateHalftoneRegion","ImmediateLosslessHalftoneRegion",null,null,null,null,null,null,null,null,null,null,null,null,"IntermediateGenericRegion",null,"ImmediateGenericRegion","ImmediateLosslessGenericRegion","IntermediateGenericRefinementRegion",null,"ImmediateGenericRefinementRegion","ImmediateLosslessGenericRefinementRegion",null,null,null,null,"PageInformation","EndOfPage","EndOfStripe","EndOfFile","Profiles","Tables",null,null,null,null,null,null,null,null,"Extension"],A=[[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:2,y:-1},{x:-4,y:0},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}],[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:2,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:2,y:-1},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}],[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-2,y:0},{x:-1,y:0}],[{x:-3,y:-1},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-4,y:0},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}]],I=[{coding:[{x:0,y:-1},{x:1,y:-1},{x:-1,y:0}],reference:[{x:0,y:-1},{x:1,y:-1},{x:-1,y:0},{x:0,y:0},{x:1,y:0},{x:-1,y:1},{x:0,y:1},{x:1,y:1}]},{coding:[{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-1,y:0}],reference:[{x:0,y:-1},{x:-1,y:0},{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}]}],B=[39717,1941,229,405],R=[32,8],T=17;C.prototype={onPageInformation:function(e){this.currentPageInfo=e;var t=e.width+7>>3,a=new Uint8Array(t*e.height);if(e.defaultPixelValue)for(var r=0,i=a.length;r<i;r++)a[r]=255;this.buffer=a},drawBitmap:function(e,t){var a,r,i,s,o=this.currentPageInfo,c=e.width,l=e.height,h=o.width+7>>3,u=o.combinationOperatorOverride?e.combinationOperator:o.combinationOperator,f=this.buffer,d=128>>(7&e.x),g=e.y*h+(e.x>>3);switch(u){case 0:for(a=0;a<l;a++){i=d;s=g;for(r=0;r<c;r++){t[a][r]&&(f[s]|=i);i>>=1;if(!i){i=128;s++}}g+=h}break;case 2:for(a=0;a<l;a++){i=d;s=g;for(r=0;r<c;r++){t[a][r]&&(f[s]^=i);i>>=1;if(!i){i=128;s++}}g+=h}break;default:n("JBIG2 error: operator "+u+" is not supported")}},onImmediateGenericRegion:function(e,a,r,i){var n=e.info,s=new t(a,r,i),o=f(e.mmr,n.width,n.height,e.template,e.prediction,null,e.at,s);this.drawBitmap(n,o)},onImmediateLosslessGenericRegion:function(){this.onImmediateGenericRegion.apply(this,arguments)},onSymbolDictionary:function(e,a,r,i,s,o){e.huffman&&n("JBIG2 error: huffman is not supported");var c=this.symbols;c||(this.symbols=c={});for(var l=[],h=0,u=r.length;h<u;h++)l=l.concat(c[r[h]]);var f=new t(i,s,o);c[a]=g(e.huffman,e.refinement,l,e.numberOfNewSymbols,e.numberOfExportedSymbols,void 0,e.template,e.at,e.refinementTemplate,e.refinementAt,f)},onImmediateTextRegion:function(e,a,r,i,n){for(var o=e.info,c=this.symbols,l=[],h=0,u=a.length;h<u;h++)l=l.concat(c[a[h]]);var f=s(l.length),d=new t(r,i,n),g=p(e.huffman,e.refinement,o.width,o.height,e.defaultPixelValue,e.numberOfSymbolInstances,e.stripSize,l,f,e.transposed,e.dsOffset,e.referenceCorner,e.combinationOperator,void 0,e.refinementTemplate,e.refinementAt,d);this.drawBitmap(o,g)},onImmediateLosslessTextRegion:function(){this.onImmediateTextRegion.apply(this,arguments)}};x.prototype={parseChunks:function(e){return w(e)}};return x}();t.Jbig2Image=f},function(e,t,a){"use strict";var r=a(0),i=r.warn,n=r.error,s=function(){function e(){this.decodeTransform=null;this.colorTransform=-1}function t(e,t){for(var a,r,i=0,n=[],s=16;s>0&&!e[s-1];)s--;n.push({children:[],index:0});var o,c=n[0];for(a=0;a<s;a++){for(r=0;r<e[a];r++){c=n.pop();c.children[c.index]=t[i];for(;c.index>0;)c=n.pop();c.index++;n.push(c);for(;n.length<=a;){n.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}i++}if(a+1<s){n.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}}return n[0].children}function a(e,t,a){return 64*((e.blocksPerLine+1)*t+a)}function r(e,t,r,s,o,c,u,f,d){function g(){if(L>0){L--;return E>>L&1}E=e[t++];if(255===E){var a=e[t++];a&&n("JPEG error: unexpected marker "+(E<<8|a).toString(16))}L=7;return E>>>7}function p(e){for(var t=e;;){t=t[g()];if("number"==typeof t)return t;"object"!=typeof t&&n("JPEG error: invalid huffman sequence")}}function m(e){for(var t=0;e>0;){t=t<<1|g();e--}return t}function b(e){if(1===e)return 1===g()?1:-1;var t=m(e);return t>=1<<e-1?t:t+(-1<<e)+1}function v(e,t){var a=p(e.huffmanTableDC),r=0===a?0:b(a);e.blockData[t]=e.pred+=r;for(var i=1;i<64;){var n=p(e.huffmanTableAC),s=15&n,o=n>>4;if(0!==s){i+=o;var c=h[i];e.blockData[t+c]=b(s);i++}else{if(o<15)break;i+=16}}}function y(e,t){var a=p(e.huffmanTableDC),r=0===a?0:b(a)<<d;e.blockData[t]=e.pred+=r}function k(e,t){e.blockData[t]|=g()<<d}function w(e,t){if(D>0)D--;else for(var a=c,r=u;a<=r;){var i=p(e.huffmanTableAC),n=15&i,s=i>>4;if(0!==n){a+=s;var o=h[a];e.blockData[t+o]=b(n)*(1<<d);a++}else{if(s<15){D=m(s)+(1<<s)-1;break}a+=16}}}function C(e,t){for(var a,r,i=c,s=u,o=0;i<=s;){var l=h[i];switch(F){case 0:r=p(e.huffmanTableAC);a=15&r;o=r>>4;if(0===a)if(o<15){D=m(o)+(1<<o);F=4}else{o=16;F=1}else{1!==a&&n("JPEG error: invalid ACn encoding");x=b(a);F=o?2:3}continue;case 1:case 2:if(e.blockData[t+l])e.blockData[t+l]+=g()<<d;else{o--;0===o&&(F=2===F?3:0)}break;case 3:if(e.blockData[t+l])e.blockData[t+l]+=g()<<d;else{e.blockData[t+l]=x<<d;F=0}break;case 4:e.blockData[t+l]&&(e.blockData[t+l]+=g()<<d)}i++}if(4===F){D--;0===D&&(F=0)}}var x,S,A,I,B,R,T,O=r.mcusPerLine,P=r.progressive,M=t,E=0,L=0,D=0,F=0,q=s.length;T=P?0===c?0===f?y:k:0===f?w:C:v;var U,N,j=0;N=1===q?s[0].blocksPerLine*s[0].blocksPerColumn:O*r.mcusPerColumn;for(var _,z;j<N;){var H=o?Math.min(N-j,o):N;for(A=0;A<q;A++)s[A].pred=0;D=0;if(1===q){S=s[0];for(R=0;R<H;R++){!function(e,t,r){t(e,a(e,r/e.blocksPerLine|0,r%e.blocksPerLine))}(S,T,j);j++}}else for(R=0;R<H;R++){for(A=0;A<q;A++){S=s[A];_=S.h;z=S.v;for(I=0;I<z;I++)for(B=0;B<_;B++)!function(e,t,r,i,n){var s=r/O|0,o=r%O;t(e,a(e,s*e.v+i,o*e.h+n))}(S,T,j,I,B)}j++}L=0;U=l(e,t);if(U&&U.invalid){i("decodeScan - unexpected MCU data, next marker is: "+U.invalid);t=U.offset}var G=U&&U.marker;(!G||G<=65280)&&n("JPEG error: marker was not found");if(!(G>=65488&&G<=65495))break;t+=2}U=l(e,t);if(U&&U.invalid){i("decodeScan - unexpected Scan data, next marker is: "+U.invalid);t=U.offset}return t-M}function s(e,t,a){var r,i,s,o,c,l,h,y,k,w,C,x,S,A,I,B,R,T=e.quantizationTable,O=e.blockData;T||n("JPEG error: missing required Quantization Table.");for(var P=0;P<64;P+=8){k=O[t+P];w=O[t+P+1];C=O[t+P+2];x=O[t+P+3];S=O[t+P+4];A=O[t+P+5];I=O[t+P+6];B=O[t+P+7];k*=T[P];if(0!=(w|C|x|S|A|I|B)){w*=T[P+1];C*=T[P+2];x*=T[P+3];S*=T[P+4];A*=T[P+5];I*=T[P+6];B*=T[P+7];r=b*k+128>>8;i=b*S+128>>8;s=C;o=I;c=v*(w-B)+128>>8;y=v*(w+B)+128>>8;l=x<<4;h=A<<4;r=r+i+1>>1;i=r-i;R=s*m+o*p+128>>8;s=s*p-o*m+128>>8;o=R;c=c+h+1>>1;h=c-h;y=y+l+1>>1;l=y-l;r=r+o+1>>1;o=r-o;i=i+s+1>>1;s=i-s;R=c*g+y*d+2048>>12;c=c*d-y*g+2048>>12;y=R;R=l*f+h*u+2048>>12;l=l*u-h*f+2048>>12;h=R;a[P]=r+y;a[P+7]=r-y;a[P+1]=i+h;a[P+6]=i-h;a[P+2]=s+l;a[P+5]=s-l;a[P+3]=o+c;a[P+4]=o-c}else{R=b*k+512>>10;a[P]=R;a[P+1]=R;a[P+2]=R;a[P+3]=R;a[P+4]=R;a[P+5]=R;a[P+6]=R;a[P+7]=R}}for(var M=0;M<8;++M){k=a[M];w=a[M+8];C=a[M+16];x=a[M+24];S=a[M+32];A=a[M+40];I=a[M+48];B=a[M+56];if(0!=(w|C|x|S|A|I|B)){r=b*k+2048>>12;i=b*S+2048>>12;s=C;o=I;c=v*(w-B)+2048>>12;y=v*(w+B)+2048>>12;l=x;h=A;r=4112+(r+i+1>>1);i=r-i;R=s*m+o*p+2048>>12;s=s*p-o*m+2048>>12;o=R;c=c+h+1>>1;h=c-h;y=y+l+1>>1;l=y-l;r=r+o+1>>1;o=r-o;i=i+s+1>>1;s=i-s;R=c*g+y*d+2048>>12;c=c*d-y*g+2048>>12;y=R;R=l*f+h*u+2048>>12;l=l*u-h*f+2048>>12;h=R;k=r+y;B=r-y;w=i+h;I=i-h;C=s+l;A=s-l;x=o+c;S=o-c;k=k<16?0:k>=4080?255:k>>4;w=w<16?0:w>=4080?255:w>>4;C=C<16?0:C>=4080?255:C>>4;x=x<16?0:x>=4080?255:x>>4;S=S<16?0:S>=4080?255:S>>4;A=A<16?0:A>=4080?255:A>>4;I=I<16?0:I>=4080?255:I>>4;B=B<16?0:B>=4080?255:B>>4;O[t+M]=k;O[t+M+8]=w;O[t+M+16]=C;O[t+M+24]=x;O[t+M+32]=S;O[t+M+40]=A;O[t+M+48]=I;O[t+M+56]=B}else{R=b*k+8192>>14;R=R<-2040?0:R>=2024?255:R+2056>>4;O[t+M]=R;O[t+M+8]=R;O[t+M+16]=R;O[t+M+24]=R;O[t+M+32]=R;O[t+M+40]=R;O[t+M+48]=R;O[t+M+56]=R}}}function o(e,t){for(var r=t.blocksPerLine,i=t.blocksPerColumn,n=new Int16Array(64),o=0;o<i;o++)for(var c=0;c<r;c++){var l=a(t,o,c);s(t,l,n)}return t.blockData}function c(e){return e<=0?0:e>=255?255:e}function l(e,t,a){function r(t){return e[t]<<8|e[t+1]}var i=e.length-1,n=a<t?a:t;if(t>=i)return null;var s=r(t);if(s>=65472&&s<=65534)return{invalid:null,marker:s,offset:t};for(var o=r(n);!(o>=65472&&o<=65534);){if(++n>=i)return null;o=r(n)}return{invalid:s.toString(16),marker:o,offset:n}}var h=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),u=4017,f=799,d=3406,g=2276,p=1567,m=3784,b=5793,v=2896;e.prototype={parse:function(e){function a(){var t=e[u]<<8|e[u+1];u+=2;return t}var s,c,u=0,f=null,d=null,g=[],p=[],m=[],b=a();65496!==b&&n("JPEG error: SOI not found");b=a();for(;65497!==b;){var v,y,k;switch(b){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514: +case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:var w=function(){var t=a(),r=u+t-2,n=l(e,r,u);if(n&&n.invalid){i("readDataBlock - incorrect length, next marker is: "+n.invalid);r=n.offset}var s=e.subarray(u,r);u+=s.length;return s}();65504===b&&74===w[0]&&70===w[1]&&73===w[2]&&70===w[3]&&0===w[4]&&(f={version:{major:w[5],minor:w[6]},densityUnits:w[7],xDensity:w[8]<<8|w[9],yDensity:w[10]<<8|w[11],thumbWidth:w[12],thumbHeight:w[13],thumbData:w.subarray(14,14+3*w[12]*w[13])});65518===b&&65===w[0]&&100===w[1]&&111===w[2]&&98===w[3]&&101===w[4]&&(d={version:w[5]<<8|w[6],flags0:w[7]<<8|w[8],flags1:w[9]<<8|w[10],transformCode:w[11]});break;case 65499:for(var C,x=a(),S=x+u-2;u<S;){var A=e[u++],I=new Uint16Array(64);if(A>>4==0)for(y=0;y<64;y++){C=h[y];I[C]=e[u++]}else if(A>>4==1)for(y=0;y<64;y++){C=h[y];I[C]=a()}else n("JPEG error: DQT - invalid table spec");g[15&A]=I}break;case 65472:case 65473:case 65474:s&&n("JPEG error: Only single frame JPEGs supported");a();s={};s.extended=65473===b;s.progressive=65474===b;s.precision=e[u++];s.scanLines=a();s.samplesPerLine=a();s.components=[];s.componentIds={};var B,R=e[u++],T=0,O=0;for(v=0;v<R;v++){B=e[u];var P=e[u+1]>>4,M=15&e[u+1];T<P&&(T=P);O<M&&(O=M);var E=e[u+2];k=s.components.push({h:P,v:M,quantizationId:E,quantizationTable:null});s.componentIds[B]=k-1;u+=3}s.maxH=T;s.maxV=O;!function(e){for(var t=Math.ceil(e.samplesPerLine/8/e.maxH),a=Math.ceil(e.scanLines/8/e.maxV),r=0;r<e.components.length;r++){N=e.components[r];var i=Math.ceil(Math.ceil(e.samplesPerLine/8)*N.h/e.maxH),n=Math.ceil(Math.ceil(e.scanLines/8)*N.v/e.maxV),s=t*N.h,o=a*N.v,c=64*o*(s+1);N.blockData=new Int16Array(c);N.blocksPerLine=i;N.blocksPerColumn=n}e.mcusPerLine=t;e.mcusPerColumn=a}(s);break;case 65476:var L=a();for(v=2;v<L;){var D=e[u++],F=new Uint8Array(16),q=0;for(y=0;y<16;y++,u++)q+=F[y]=e[u];var U=new Uint8Array(q);for(y=0;y<q;y++,u++)U[y]=e[u];v+=17+q;(D>>4==0?m:p)[15&D]=t(F,U)}break;case 65501:a();c=a();break;case 65498:a();var N,j=e[u++],_=[];for(v=0;v<j;v++){var z=s.componentIds[e[u++]];N=s.components[z];var H=e[u++];N.huffmanTableDC=m[H>>4];N.huffmanTableAC=p[15&H];_.push(N)}var G=e[u++],X=e[u++],V=e[u++],W=r(e,u,s,_,c,G,X,V>>4,15&V);u+=W;break;case 65535:255!==e[u]&&u--;break;default:if(255===e[u-3]&&e[u-2]>=192&&e[u-2]<=254){u-=3;break}n("JPEG error: unknown marker "+b.toString(16))}b=a()}this.width=s.samplesPerLine;this.height=s.scanLines;this.jfif=f;this.adobe=d;this.components=[];for(v=0;v<s.components.length;v++){N=s.components[v];var K=g[N.quantizationId];K&&(N.quantizationTable=K);this.components.push({output:o(s,N),scaleX:N.h/s.maxH,scaleY:N.v/s.maxV,blocksPerLine:N.blocksPerLine,blocksPerColumn:N.blocksPerColumn})}this.numComponents=this.components.length},_getLinearizedBlockData:function(e,t){var a,r,i,n,s,o,c,l,h,u,f,d=this.width/e,g=this.height/t,p=0,m=this.components.length,b=e*t*m,v=new Uint8Array(b),y=new Uint32Array(e);for(c=0;c<m;c++){a=this.components[c];r=a.scaleX*d;i=a.scaleY*g;p=c;f=a.output;n=a.blocksPerLine+1<<3;for(s=0;s<e;s++){l=0|s*r;y[s]=(4294967288&l)<<3|7&l}for(o=0;o<t;o++){l=0|o*i;u=n*(4294967288&l)|(7&l)<<3;for(s=0;s<e;s++){v[p]=f[u+y[s]];p+=m}}}var k=this.decodeTransform;if(k)for(c=0;c<b;)for(l=0,h=0;l<m;l++,c++,h+=2)v[c]=(v[c]*k[h]>>8)+k[h+1];return v},_isColorConversionNeeded:function(){return!(!this.adobe||!this.adobe.transformCode)||(3===this.numComponents?!(!this.adobe&&0===this.colorTransform):!this.adobe&&1===this.colorTransform)},_convertYccToRgb:function(e){for(var t,a,r,i=0,n=e.length;i<n;i+=3){t=e[i];a=e[i+1];r=e[i+2];e[i]=c(t-179.456+1.402*r);e[i+1]=c(t+135.459-.344*a-.714*r);e[i+2]=c(t-226.816+1.772*a)}return e},_convertYcckToRgb:function(e){for(var t,a,r,i,n=0,s=0,o=e.length;s<o;s+=4){t=e[s];a=e[s+1];r=e[s+2];i=e[s+3];var l=a*(-660635669420364e-19*a+.000437130475926232*r-54080610064599e-18*t+.00048449797120281*i-.154362151871126)-122.67195406894+r*(-.000957964378445773*r+.000817076911346625*t-.00477271405408747*i+1.53380253221734)+t*(.000961250184130688*t-.00266257332283933*i+.48357088451265)+i*(-.000336197177618394*i+.484791561490776),h=107.268039397724+a*(219927104525741e-19*a-.000640992018297945*r+.000659397001245577*t+.000426105652938837*i-.176491792462875)+r*(-.000778269941513683*r+.00130872261408275*t+.000770482631801132*i-.151051492775562)+t*(.00126935368114843*t-.00265090189010898*i+.25802910206845)+i*(-.000318913117588328*i-.213742400323665),u=a*(-.000570115196973677*a-263409051004589e-19*r+.0020741088115012*t-.00288260236853442*i+.814272968359295)-20.810012546947+r*(-153496057440975e-19*r-.000132689043961446*t+.000560833691242812*i-.195152027534049)+t*(.00174418132927582*t-.00255243321439347*i+.116935020465145)+i*(-.000343531996510555*i+.24165260232407);e[n++]=c(l);e[n++]=c(h);e[n++]=c(u)}return e},_convertYcckToCmyk:function(e){for(var t,a,r,i=0,n=e.length;i<n;i+=4){t=e[i];a=e[i+1];r=e[i+2];e[i]=c(434.456-t-1.402*r);e[i+1]=c(119.541-t+.344*a+.714*r);e[i+2]=c(481.816-t-1.772*a)}return e},_convertCmykToRgb:function(e){for(var t,a,r,i,n=0,s=-16581375,o=0,c=e.length;o<c;o+=4){t=e[o];a=e[o+1];r=e[o+2];i=e[o+3];var l=t*(-4.387332384609988*t+54.48615194189176*a+18.82290502165302*r+212.25662451639585*i-72734.4411664936)+a*(1.7149763477362134*a-5.6096736904047315*r-17.873870861415444*i-1401.7366389350734)+r*(-2.5217340131683033*r-21.248923337353073*i+4465.541406466231)-i*(21.86122147463605*i+48317.86113160301),h=t*(8.841041422036149*t+60.118027045597366*a+6.871425592049007*r+31.159100130055922*i-20220.756542821975)+a*(-15.310361306967817*a+17.575251261109482*r+131.35250912493976*i-48691.05921601825)+r*(4.444339102852739*r+9.8632861493405*i-6341.191035517494)-i*(20.737325471181034*i+47890.15695978492),u=t*(.8842522430003296*t+8.078677503112928*a+30.89978309703729*r-.23883238689178934*i-3616.812083916688)+a*(10.49593273432072*a+63.02378494754052*r+50.606957656360734*i-28620.90484698408)+r*(.03296041114873217*r+115.60384449646641*i-49363.43385999684)-i*(22.33816807309886*i+45932.16563550634);e[n++]=l>=0?255:l<=s?0:255+l*(1/255/255)|0;e[n++]=h>=0?255:h<=s?0:255+h*(1/255/255)|0;e[n++]=u>=0?255:u<=s?0:255+u*(1/255/255)|0}return e},getData:function(e,t,a){this.numComponents>4&&n("JPEG error: Unsupported color mode");var r=this._getLinearizedBlockData(e,t);if(1===this.numComponents&&a){for(var i=r.length,s=new Uint8Array(3*i),o=0,c=0;c<i;c++){var l=r[c];s[o++]=l;s[o++]=l;s[o++]=l}return s}if(3===this.numComponents&&this._isColorConversionNeeded())return this._convertYccToRgb(r);if(4===this.numComponents){if(this._isColorConversionNeeded())return a?this._convertYcckToRgb(r):this._convertYcckToCmyk(r);if(a)return this._convertCmykToRgb(r)}return r}};return e}();t.JpegImage=s},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.Courier=600;e["Courier-Bold"]=600;e["Courier-BoldOblique"]=600;e["Courier-Oblique"]=600;e.Helvetica=i(function(e){e.space=278;e.exclam=278;e.quotedbl=355;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=667;e.quoteright=222;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=278;e.semicolon=278;e.less=584;e.equal=584;e.greater=584;e.question=556;e.at=1015;e.A=667;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=500;e.K=667;e.L=556;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=278;e.backslash=278;e.bracketright=278;e.asciicircum=469;e.underscore=556;e.quoteleft=222;e.a=556;e.b=556;e.c=500;e.d=556;e.e=556;e.f=278;e.g=556;e.h=556;e.i=222;e.j=222;e.k=500;e.l=222;e.m=833;e.n=556;e.o=556;e.p=556;e.q=556;e.r=333;e.s=500;e.t=278;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=500;e.braceleft=334;e.bar=260;e.braceright=334;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=191;e.quotedblleft=333;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=537;e.bullet=350;e.quotesinglbase=222;e.quotedblbase=333;e.quotedblright=333;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=556;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=222;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=556;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=667;e.aacute=556;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=500;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=500;e.aring=556;e.Ncommaaccent=722;e.lacute=222;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=500;e.scedilla=500;e.iacute=278;e.lozenge=471;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=556;e.Amacron=667;e.rcaron=333;e.ccedilla=500;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=643;e.Umacron=722;e.uring=556;e.threesuperior=333;e.Ograve=778;e.Agrave=667;e.Abreve=667;e.multiply=584;e.uacute=556;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=500;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=260;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=333;e.omacron=556;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=222;e.tcaron=317;e.eogonek=556;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=556;e.zacute=500;e.iogonek=222;e.Oacute=778;e.oacute=556;e.amacron=556;e.sacute=500;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=333;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=556;e.Eogonek=667;e.dcroat=556;e.threequarters=834;e.Scedilla=667;e.lcaron=299;e.Kcommaaccent=667;e.Lacute=556;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=556;e.onehalf=834;e.lessequal=549;e.ocircumflex=556;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=556;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=556;e.Ccaron=722;e.ugrave=556;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=556;e.Rcommaaccent=722;e.Lcommaaccent=556;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=500;e.minus=584;e.Icircumflex=278;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=584;e.odieresis=556;e.udieresis=556;e.notequal=549;e.gcommaaccent=556;e.eth=556;e.zcaron=500;e.ncommaaccent=556;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-Bold"]=i(function(e){e.space=278;e.exclam=333;e.quotedbl=474;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=722;e.quoteright=278;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=333;e.semicolon=333;e.less=584;e.equal=584;e.greater=584;e.question=611;e.at=975;e.A=722;e.B=722;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=556;e.K=722;e.L=611;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=584;e.underscore=556;e.quoteleft=278;e.a=556;e.b=611;e.c=556;e.d=611;e.e=556;e.f=333;e.g=611;e.h=611;e.i=278;e.j=278;e.k=556;e.l=278;e.m=889;e.n=611;e.o=611;e.p=611;e.q=611;e.r=389;e.s=556;e.t=333;e.u=611;e.v=556;e.w=778;e.x=556;e.y=556;e.z=500;e.braceleft=389;e.bar=280;e.braceright=389;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=238;e.quotedblleft=500;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=611;e.fl=611;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=556;e.bullet=350;e.quotesinglbase=278;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=611;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=278;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=611;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=722;e.aacute=556;e.Ucircumflex=722;e.yacute=556;e.scommaaccent=556;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=611;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=556;e.aring=556;e.Ncommaaccent=722;e.lacute=278;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=556;e.scedilla=556;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=611;e.acircumflex=556;e.Amacron=722;e.rcaron=389;e.ccedilla=556;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=743;e.Umacron=722;e.uring=611;e.threesuperior=333;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=584;e.uacute=611;e.Tcaron=611;e.partialdiff=494;e.ydieresis=556;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=556;e.nacute=611;e.umacron=611;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=280;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=611;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=389;e.eogonek=556;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=556;e.zacute=500;e.iogonek=278;e.Oacute=778;e.oacute=611;e.amacron=556;e.sacute=556;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=611;e.twosuperior=333;e.Odieresis=778;e.mu=611;e.igrave=278;e.ohungarumlaut=611;e.Eogonek=667;e.dcroat=611;e.threequarters=834;e.Scedilla=667;e.lcaron=400;e.Kcommaaccent=722;e.Lacute=611;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=611;e.onehalf=834;e.lessequal=549;e.ocircumflex=611;e.ntilde=611;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=611;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=611;e.Ccaron=722;e.ugrave=611;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=611;e.Rcommaaccent=722;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=556;e.minus=584;e.Icircumflex=278;e.ncaron=611;e.tcommaaccent=333;e.logicalnot=584;e.odieresis=611;e.udieresis=611;e.notequal=549;e.gcommaaccent=611;e.eth=611;e.zcaron=500;e.ncommaaccent=611;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-BoldOblique"]=i(function(e){e.space=278;e.exclam=333;e.quotedbl=474;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=722;e.quoteright=278;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=333;e.semicolon=333;e.less=584;e.equal=584;e.greater=584;e.question=611;e.at=975;e.A=722;e.B=722;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=556;e.K=722;e.L=611;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=584;e.underscore=556;e.quoteleft=278;e.a=556;e.b=611;e.c=556;e.d=611;e.e=556;e.f=333;e.g=611;e.h=611;e.i=278;e.j=278;e.k=556;e.l=278;e.m=889;e.n=611;e.o=611;e.p=611;e.q=611;e.r=389;e.s=556;e.t=333;e.u=611;e.v=556;e.w=778;e.x=556;e.y=556;e.z=500;e.braceleft=389;e.bar=280;e.braceright=389;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=238;e.quotedblleft=500;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=611;e.fl=611;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=556;e.bullet=350;e.quotesinglbase=278;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=611;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=278;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=611;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=722;e.aacute=556;e.Ucircumflex=722;e.yacute=556;e.scommaaccent=556;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=611;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=556;e.aring=556;e.Ncommaaccent=722;e.lacute=278;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=556;e.scedilla=556;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=611;e.acircumflex=556;e.Amacron=722;e.rcaron=389;e.ccedilla=556;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=743;e.Umacron=722;e.uring=611;e.threesuperior=333;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=584;e.uacute=611;e.Tcaron=611;e.partialdiff=494;e.ydieresis=556;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=556;e.nacute=611;e.umacron=611;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=280;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=611;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=389;e.eogonek=556;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=556;e.zacute=500;e.iogonek=278;e.Oacute=778;e.oacute=611;e.amacron=556;e.sacute=556;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=611;e.twosuperior=333;e.Odieresis=778;e.mu=611;e.igrave=278;e.ohungarumlaut=611;e.Eogonek=667;e.dcroat=611;e.threequarters=834;e.Scedilla=667;e.lcaron=400;e.Kcommaaccent=722;e.Lacute=611;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=611;e.onehalf=834;e.lessequal=549;e.ocircumflex=611;e.ntilde=611;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=611;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=611;e.Ccaron=722;e.ugrave=611;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=611;e.Rcommaaccent=722;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=556;e.minus=584;e.Icircumflex=278;e.ncaron=611;e.tcommaaccent=333;e.logicalnot=584;e.odieresis=611;e.udieresis=611;e.notequal=549;e.gcommaaccent=611;e.eth=611;e.zcaron=500;e.ncommaaccent=611;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-Oblique"]=i(function(e){e.space=278;e.exclam=278;e.quotedbl=355;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=667;e.quoteright=222;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=278;e.semicolon=278;e.less=584;e.equal=584;e.greater=584;e.question=556;e.at=1015;e.A=667;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=500;e.K=667;e.L=556;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=278;e.backslash=278;e.bracketright=278;e.asciicircum=469;e.underscore=556;e.quoteleft=222;e.a=556;e.b=556;e.c=500;e.d=556;e.e=556;e.f=278;e.g=556;e.h=556;e.i=222;e.j=222;e.k=500;e.l=222;e.m=833;e.n=556;e.o=556;e.p=556;e.q=556;e.r=333;e.s=500;e.t=278;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=500;e.braceleft=334;e.bar=260;e.braceright=334;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=191;e.quotedblleft=333;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=537;e.bullet=350;e.quotesinglbase=222;e.quotedblbase=333;e.quotedblright=333;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=556;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=222;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=556;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=667;e.aacute=556;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=500;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=500;e.aring=556;e.Ncommaaccent=722;e.lacute=222;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=500;e.scedilla=500;e.iacute=278;e.lozenge=471;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=556;e.Amacron=667;e.rcaron=333;e.ccedilla=500;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=643;e.Umacron=722;e.uring=556;e.threesuperior=333;e.Ograve=778;e.Agrave=667;e.Abreve=667;e.multiply=584;e.uacute=556;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=500;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=260;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=333;e.omacron=556;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=222;e.tcaron=317;e.eogonek=556;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=556;e.zacute=500;e.iogonek=222;e.Oacute=778;e.oacute=556;e.amacron=556;e.sacute=500;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=333;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=556;e.Eogonek=667;e.dcroat=556;e.threequarters=834;e.Scedilla=667;e.lcaron=299;e.Kcommaaccent=667;e.Lacute=556;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=556;e.onehalf=834;e.lessequal=549;e.ocircumflex=556;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=556;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=556;e.Ccaron=722;e.ugrave=556;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=556;e.Rcommaaccent=722;e.Lcommaaccent=556;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=500;e.minus=584;e.Icircumflex=278;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=584;e.odieresis=556;e.udieresis=556;e.notequal=549;e.gcommaaccent=556;e.eth=556;e.zcaron=500;e.ncommaaccent=556;e.onesuperior=333;e.imacron=278;e.Euro=556});e.Symbol=i(function(e){e.space=250;e.exclam=333;e.universal=713;e.numbersign=500;e.existential=549;e.percent=833;e.ampersand=778;e.suchthat=439;e.parenleft=333;e.parenright=333;e.asteriskmath=500;e.plus=549;e.comma=250;e.minus=549;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=278;e.semicolon=278;e.less=549;e.equal=549;e.greater=549;e.question=444;e.congruent=549;e.Alpha=722;e.Beta=667;e.Chi=722;e.Delta=612;e.Epsilon=611;e.Phi=763;e.Gamma=603;e.Eta=722;e.Iota=333;e.theta1=631;e.Kappa=722;e.Lambda=686;e.Mu=889;e.Nu=722;e.Omicron=722;e.Pi=768;e.Theta=741;e.Rho=556;e.Sigma=592;e.Tau=611;e.Upsilon=690;e.sigma1=439;e.Omega=768;e.Xi=645;e.Psi=795;e.Zeta=611;e.bracketleft=333;e.therefore=863;e.bracketright=333;e.perpendicular=658;e.underscore=500;e.radicalex=500;e.alpha=631;e.beta=549;e.chi=549;e.delta=494;e.epsilon=439;e.phi=521;e.gamma=411;e.eta=603;e.iota=329;e.phi1=603;e.kappa=549;e.lambda=549;e.mu=576;e.nu=521;e.omicron=549;e.pi=549;e.theta=521;e.rho=549;e.sigma=603;e.tau=439;e.upsilon=576;e.omega1=713;e.omega=686;e.xi=493;e.psi=686;e.zeta=494;e.braceleft=480;e.bar=200;e.braceright=480;e.similar=549;e.Euro=750;e.Upsilon1=620;e.minute=247;e.lessequal=549;e.fraction=167;e.infinity=713;e.florin=500;e.club=753;e.diamond=753;e.heart=753;e.spade=753;e.arrowboth=1042;e.arrowleft=987;e.arrowup=603;e.arrowright=987;e.arrowdown=603;e.degree=400;e.plusminus=549;e.second=411;e.greaterequal=549;e.multiply=549;e.proportional=713;e.partialdiff=494;e.bullet=460;e.divide=549;e.notequal=549;e.equivalence=549;e.approxequal=549;e.ellipsis=1e3;e.arrowvertex=603;e.arrowhorizex=1e3;e.carriagereturn=658;e.aleph=823;e.Ifraktur=686;e.Rfraktur=795;e.weierstrass=987;e.circlemultiply=768;e.circleplus=768;e.emptyset=823;e.intersection=768;e.union=768;e.propersuperset=713;e.reflexsuperset=713;e.notsubset=713;e.propersubset=713;e.reflexsubset=713;e.element=713;e.notelement=713;e.angle=768;e.gradient=713;e.registerserif=790;e.copyrightserif=790;e.trademarkserif=890;e.product=823;e.radical=549;e.dotmath=250;e.logicalnot=713;e.logicaland=603;e.logicalor=603;e.arrowdblboth=1042;e.arrowdblleft=987;e.arrowdblup=603;e.arrowdblright=987;e.arrowdbldown=603;e.lozenge=494;e.angleleft=329;e.registersans=790;e.copyrightsans=790;e.trademarksans=786;e.summation=713;e.parenlefttp=384;e.parenleftex=384;e.parenleftbt=384;e.bracketlefttp=384;e.bracketleftex=384;e.bracketleftbt=384;e.bracelefttp=494;e.braceleftmid=494;e.braceleftbt=494;e.braceex=494;e.angleright=329;e.integral=274;e.integraltp=686;e.integralex=686;e.integralbt=686;e.parenrighttp=384;e.parenrightex=384;e.parenrightbt=384;e.bracketrighttp=384;e.bracketrightex=384;e.bracketrightbt=384;e.bracerighttp=494;e.bracerightmid=494;e.bracerightbt=494;e.apple=790});e["Times-Roman"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=408;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=564;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=278;e.semicolon=278;e.less=564;e.equal=564;e.greater=564;e.question=444;e.at=921;e.A=722;e.B=667;e.C=667;e.D=722;e.E=611;e.F=556;e.G=722;e.H=722;e.I=333;e.J=389;e.K=722;e.L=611;e.M=889;e.N=722;e.O=722;e.P=556;e.Q=722;e.R=667;e.S=556;e.T=611;e.U=722;e.V=722;e.W=944;e.X=722;e.Y=722;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=469;e.underscore=500;e.quoteleft=333;e.a=444;e.b=500;e.c=444;e.d=500;e.e=444;e.f=333;e.g=500;e.h=500;e.i=278;e.j=278;e.k=500;e.l=278;e.m=778;e.n=500;e.o=500;e.p=500;e.q=500;e.r=333;e.s=389;e.t=278;e.u=500;e.v=500;e.w=722;e.x=500;e.y=500;e.z=444;e.braceleft=480;e.bar=200;e.braceright=480;e.asciitilde=541;e.exclamdown=333;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=180;e.quotedblleft=444;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=453;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=444;e.quotedblright=444;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=444;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=889;e.ordfeminine=276;e.Lslash=611;e.Oslash=722;e.OE=889;e.ordmasculine=310;e.ae=667;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=500;e.Idieresis=333;e.eacute=444;e.abreve=444;e.uhungarumlaut=500;e.ecaron=444;e.Ydieresis=722;e.divide=564;e.Yacute=722;e.Acircumflex=722;e.aacute=444;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=444;e.Uacute=722;e.uogonek=500;e.Edieresis=611;e.Dcroat=722;e.commaaccent=250;e.copyright=760;e.Emacron=611;e.ccaron=444;e.aring=444;e.Ncommaaccent=722;e.lacute=278;e.agrave=444;e.Tcommaaccent=611;e.Cacute=667;e.atilde=444;e.Edotaccent=611;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=471;e.Rcaron=667;e.Gcommaaccent=722;e.ucircumflex=500;e.acircumflex=444;e.Amacron=722;e.rcaron=333;e.ccedilla=444;e.Zdotaccent=611;e.Thorn=556;e.Omacron=722;e.Racute=667;e.Sacute=556;e.dcaron=588;e.Umacron=722;e.uring=500;e.threesuperior=300;e.Ograve=722;e.Agrave=722;e.Abreve=722;e.multiply=564;e.uacute=500;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=611;e.adieresis=444;e.edieresis=444;e.cacute=444;e.nacute=500;e.umacron=500;e.Ncaron=722;e.Iacute=333;e.plusminus=564;e.brokenbar=200;e.registered=760;e.Gbreve=722;e.Idotaccent=333;e.summation=600;e.Egrave=611;e.racute=333;e.omacron=500;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=326;e.eogonek=444;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=444;e.zacute=444;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=444;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=500;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=611;e.dcroat=500;e.threequarters=750;e.Scedilla=556;e.lcaron=344;e.Kcommaaccent=722;e.Lacute=611;e.trademark=980;e.edotaccent=444;e.Igrave=333;e.Imacron=333;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=500;e.Uhungarumlaut=722;e.Eacute=611;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=500;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=667;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=722;e.zdotaccent=444;e.Ecaron=611;e.Iogonek=333;e.kcommaaccent=500;e.minus=564;e.Icircumflex=333;e.ncaron=500;e.tcommaaccent=278;e.logicalnot=564;e.odieresis=500;e.udieresis=500;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=444;e.ncommaaccent=500;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-Bold"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=555;e.numbersign=500;e.dollar=500;e.percent=1e3;e.ampersand=833;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=570;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=570;e.equal=570;e.greater=570;e.question=500;e.at=930;e.A=722;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=778;e.I=389;e.J=500;e.K=778;e.L=667;e.M=944;e.N=722;e.O=778;e.P=611;e.Q=778;e.R=722;e.S=556;e.T=667;e.U=722;e.V=722;e.W=1e3;e.X=722;e.Y=722;e.Z=667;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=581;e.underscore=500;e.quoteleft=333;e.a=500;e.b=556;e.c=444;e.d=556;e.e=444;e.f=333;e.g=500;e.h=556;e.i=278;e.j=333 +;e.k=556;e.l=278;e.m=833;e.n=556;e.o=500;e.p=556;e.q=556;e.r=444;e.s=389;e.t=333;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=444;e.braceleft=394;e.bar=220;e.braceright=394;e.asciitilde=520;e.exclamdown=333;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=278;e.quotedblleft=500;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=540;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=300;e.Lslash=667;e.Oslash=778;e.OE=1e3;e.ordmasculine=330;e.ae=722;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=556;e.Idieresis=389;e.eacute=444;e.abreve=500;e.uhungarumlaut=556;e.ecaron=444;e.Ydieresis=722;e.divide=570;e.Yacute=722;e.Acircumflex=722;e.aacute=500;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=747;e.Emacron=667;e.ccaron=444;e.aring=500;e.Ncommaaccent=722;e.lacute=278;e.agrave=500;e.Tcommaaccent=667;e.Cacute=722;e.atilde=500;e.Edotaccent=667;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=500;e.Amacron=722;e.rcaron=444;e.ccedilla=444;e.Zdotaccent=667;e.Thorn=611;e.Omacron=778;e.Racute=722;e.Sacute=556;e.dcaron=672;e.Umacron=722;e.uring=556;e.threesuperior=300;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=570;e.uacute=556;e.Tcaron=667;e.partialdiff=494;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=389;e.plusminus=570;e.brokenbar=220;e.registered=747;e.Gbreve=778;e.Idotaccent=389;e.summation=600;e.Egrave=667;e.racute=444;e.omacron=500;e.Zacute=667;e.Zcaron=667;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=416;e.eogonek=444;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=444;e.zacute=444;e.iogonek=278;e.Oacute=778;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=300;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=667;e.dcroat=556;e.threequarters=750;e.Scedilla=556;e.lcaron=394;e.Kcommaaccent=778;e.Lacute=667;e.trademark=1e3;e.edotaccent=444;e.Igrave=389;e.Imacron=389;e.Lcaron=667;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=778;e.degree=400;e.ograve=500;e.Ccaron=722;e.ugrave=556;e.radical=549;e.Dcaron=722;e.rcommaaccent=444;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=722;e.Lcommaaccent=667;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=444;e.Ecaron=667;e.Iogonek=389;e.kcommaaccent=556;e.minus=570;e.Icircumflex=389;e.ncaron=556;e.tcommaaccent=333;e.logicalnot=570;e.odieresis=500;e.udieresis=556;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=444;e.ncommaaccent=556;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-BoldItalic"]=i(function(e){e.space=250;e.exclam=389;e.quotedbl=555;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=570;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=570;e.equal=570;e.greater=570;e.question=500;e.at=832;e.A=667;e.B=667;e.C=667;e.D=722;e.E=667;e.F=667;e.G=722;e.H=778;e.I=389;e.J=500;e.K=667;e.L=611;e.M=889;e.N=722;e.O=722;e.P=611;e.Q=722;e.R=667;e.S=556;e.T=611;e.U=722;e.V=667;e.W=889;e.X=667;e.Y=611;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=570;e.underscore=500;e.quoteleft=333;e.a=500;e.b=500;e.c=444;e.d=500;e.e=444;e.f=333;e.g=500;e.h=556;e.i=278;e.j=278;e.k=500;e.l=278;e.m=778;e.n=556;e.o=500;e.p=500;e.q=500;e.r=389;e.s=389;e.t=278;e.u=556;e.v=444;e.w=667;e.x=500;e.y=444;e.z=389;e.braceleft=348;e.bar=220;e.braceright=348;e.asciitilde=570;e.exclamdown=389;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=278;e.quotedblleft=500;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=500;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=944;e.ordfeminine=266;e.Lslash=611;e.Oslash=722;e.OE=944;e.ordmasculine=300;e.ae=722;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=500;e.Idieresis=389;e.eacute=444;e.abreve=500;e.uhungarumlaut=556;e.ecaron=444;e.Ydieresis=611;e.divide=570;e.Yacute=611;e.Acircumflex=667;e.aacute=500;e.Ucircumflex=722;e.yacute=444;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=747;e.Emacron=667;e.ccaron=444;e.aring=500;e.Ncommaaccent=722;e.lacute=278;e.agrave=500;e.Tcommaaccent=611;e.Cacute=667;e.atilde=500;e.Edotaccent=667;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=494;e.Rcaron=667;e.Gcommaaccent=722;e.ucircumflex=556;e.acircumflex=500;e.Amacron=667;e.rcaron=389;e.ccedilla=444;e.Zdotaccent=611;e.Thorn=611;e.Omacron=722;e.Racute=667;e.Sacute=556;e.dcaron=608;e.Umacron=722;e.uring=556;e.threesuperior=300;e.Ograve=722;e.Agrave=667;e.Abreve=667;e.multiply=570;e.uacute=556;e.Tcaron=611;e.partialdiff=494;e.ydieresis=444;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=389;e.plusminus=570;e.brokenbar=220;e.registered=747;e.Gbreve=722;e.Idotaccent=389;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=500;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=366;e.eogonek=444;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=444;e.zacute=389;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=576;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=667;e.dcroat=500;e.threequarters=750;e.Scedilla=556;e.lcaron=382;e.Kcommaaccent=667;e.Lacute=611;e.trademark=1e3;e.edotaccent=444;e.Igrave=389;e.Imacron=389;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=556;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=667;e.Lcommaaccent=611;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=722;e.zdotaccent=389;e.Ecaron=667;e.Iogonek=389;e.kcommaaccent=500;e.minus=606;e.Icircumflex=389;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=606;e.odieresis=500;e.udieresis=556;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=389;e.ncommaaccent=556;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-Italic"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=420;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=675;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=675;e.equal=675;e.greater=675;e.question=500;e.at=920;e.A=611;e.B=611;e.C=667;e.D=722;e.E=611;e.F=611;e.G=722;e.H=722;e.I=333;e.J=444;e.K=667;e.L=556;e.M=833;e.N=667;e.O=722;e.P=611;e.Q=722;e.R=611;e.S=500;e.T=556;e.U=722;e.V=611;e.W=833;e.X=611;e.Y=556;e.Z=556;e.bracketleft=389;e.backslash=278;e.bracketright=389;e.asciicircum=422;e.underscore=500;e.quoteleft=333;e.a=500;e.b=500;e.c=444;e.d=500;e.e=444;e.f=278;e.g=500;e.h=500;e.i=278;e.j=278;e.k=444;e.l=278;e.m=722;e.n=500;e.o=500;e.p=500;e.q=500;e.r=389;e.s=389;e.t=278;e.u=500;e.v=444;e.w=667;e.x=444;e.y=444;e.z=389;e.braceleft=400;e.bar=275;e.braceright=400;e.asciitilde=541;e.exclamdown=389;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=214;e.quotedblleft=556;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=523;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=556;e.quotedblright=556;e.guillemotright=500;e.ellipsis=889;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=889;e.AE=889;e.ordfeminine=276;e.Lslash=556;e.Oslash=722;e.OE=944;e.ordmasculine=310;e.ae=667;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=667;e.germandbls=500;e.Idieresis=333;e.eacute=444;e.abreve=500;e.uhungarumlaut=500;e.ecaron=444;e.Ydieresis=556;e.divide=675;e.Yacute=556;e.Acircumflex=611;e.aacute=500;e.Ucircumflex=722;e.yacute=444;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=500;e.Edieresis=611;e.Dcroat=722;e.commaaccent=250;e.copyright=760;e.Emacron=611;e.ccaron=444;e.aring=500;e.Ncommaaccent=667;e.lacute=278;e.agrave=500;e.Tcommaaccent=556;e.Cacute=667;e.atilde=500;e.Edotaccent=611;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=471;e.Rcaron=611;e.Gcommaaccent=722;e.ucircumflex=500;e.acircumflex=500;e.Amacron=611;e.rcaron=389;e.ccedilla=444;e.Zdotaccent=556;e.Thorn=611;e.Omacron=722;e.Racute=611;e.Sacute=500;e.dcaron=544;e.Umacron=722;e.uring=500;e.threesuperior=300;e.Ograve=722;e.Agrave=611;e.Abreve=611;e.multiply=675;e.uacute=500;e.Tcaron=556;e.partialdiff=476;e.ydieresis=444;e.Nacute=667;e.icircumflex=278;e.Ecircumflex=611;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=500;e.umacron=500;e.Ncaron=667;e.Iacute=333;e.plusminus=675;e.brokenbar=275;e.registered=760;e.Gbreve=722;e.Idotaccent=333;e.summation=600;e.Egrave=611;e.racute=389;e.omacron=500;e.Zacute=556;e.Zcaron=556;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=300;e.eogonek=444;e.Uogonek=722;e.Aacute=611;e.Adieresis=611;e.egrave=444;e.zacute=389;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=500;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=611;e.dcroat=500;e.threequarters=750;e.Scedilla=500;e.lcaron=300;e.Kcommaaccent=667;e.Lacute=556;e.trademark=980;e.edotaccent=444;e.Igrave=333;e.Imacron=333;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=500;e.Uhungarumlaut=722;e.Eacute=611;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=500;e.Scommaaccent=500;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=500;e.radical=453;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=667;e.otilde=500;e.Rcommaaccent=611;e.Lcommaaccent=556;e.Atilde=611;e.Aogonek=611;e.Aring=611;e.Otilde=722;e.zdotaccent=389;e.Ecaron=611;e.Iogonek=333;e.kcommaaccent=444;e.minus=675;e.Icircumflex=333;e.ncaron=500;e.tcommaaccent=278;e.logicalnot=675;e.odieresis=500;e.udieresis=500;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=389;e.ncommaaccent=500;e.onesuperior=300;e.imacron=278;e.Euro=500});e.ZapfDingbats=i(function(e){e.space=278;e.a1=974;e.a2=961;e.a202=974;e.a3=980;e.a4=719;e.a5=789;e.a119=790;e.a118=791;e.a117=690;e.a11=960;e.a12=939;e.a13=549;e.a14=855;e.a15=911;e.a16=933;e.a105=911;e.a17=945;e.a18=974;e.a19=755;e.a20=846;e.a21=762;e.a22=761;e.a23=571;e.a24=677;e.a25=763;e.a26=760;e.a27=759;e.a28=754;e.a6=494;e.a7=552;e.a8=537;e.a9=577;e.a10=692;e.a29=786;e.a30=788;e.a31=788;e.a32=790;e.a33=793;e.a34=794;e.a35=816;e.a36=823;e.a37=789;e.a38=841;e.a39=823;e.a40=833;e.a41=816;e.a42=831;e.a43=923;e.a44=744;e.a45=723;e.a46=749;e.a47=790;e.a48=792;e.a49=695;e.a50=776;e.a51=768;e.a52=792;e.a53=759;e.a54=707;e.a55=708;e.a56=682;e.a57=701;e.a58=826;e.a59=815;e.a60=789;e.a61=789;e.a62=707;e.a63=687;e.a64=696;e.a65=689;e.a66=786;e.a67=787;e.a68=713;e.a69=791;e.a70=785;e.a71=791;e.a72=873;e.a73=761;e.a74=762;e.a203=762;e.a75=759;e.a204=759;e.a76=892;e.a77=892;e.a78=788;e.a79=784;e.a81=438;e.a82=138;e.a83=277;e.a84=415;e.a97=392;e.a98=392;e.a99=668;e.a100=668;e.a89=390;e.a90=390;e.a93=317;e.a94=317;e.a91=276;e.a92=276;e.a205=509;e.a85=509;e.a206=410;e.a86=410;e.a87=234;e.a88=234;e.a95=334;e.a96=334;e.a101=732;e.a102=544;e.a103=544;e.a104=910;e.a106=667;e.a107=760;e.a108=760;e.a112=776;e.a111=595;e.a110=694;e.a109=626;e.a120=788;e.a121=788;e.a122=788;e.a123=788;e.a124=788;e.a125=788;e.a126=788;e.a127=788;e.a128=788;e.a129=788;e.a130=788;e.a131=788;e.a132=788;e.a133=788;e.a134=788;e.a135=788;e.a136=788;e.a137=788;e.a138=788;e.a139=788;e.a140=788;e.a141=788;e.a142=788;e.a143=788;e.a144=788;e.a145=788;e.a146=788;e.a147=788;e.a148=788;e.a149=788;e.a150=788;e.a151=788;e.a152=788;e.a153=788;e.a154=788;e.a155=788;e.a156=788;e.a157=788;e.a158=788;e.a159=788;e.a160=894;e.a161=838;e.a163=1016;e.a164=458;e.a196=748;e.a165=924;e.a192=748;e.a166=918;e.a167=927;e.a168=928;e.a169=928;e.a170=834;e.a171=873;e.a172=828;e.a173=924;e.a162=924;e.a174=917;e.a175=930;e.a176=931;e.a177=463;e.a178=883;e.a179=836;e.a193=836;e.a180=867;e.a199=867;e.a181=696;e.a200=696;e.a182=874;e.a201=874;e.a183=760;e.a184=946;e.a197=771;e.a185=865;e.a194=771;e.a198=888;e.a186=967;e.a195=888;e.a187=831;e.a188=873;e.a189=927;e.a190=970;e.a191=918})});t.getMetrics=n},function(e,t,a){"use strict";var r=a(0),i=r.Uint32ArrayView,n=function(e){function t(e){this.h1=e?4294967295&e:3285377520;this.h2=e?4294967295&e:3285377520}var a=!1;try{new Uint32Array(new Uint8Array(5).buffer,0,1)}catch(e){a=!0}t.prototype={update:function(e){var t,r=a;if("string"==typeof e){var n=new Uint8Array(2*e.length),s=0;for(t=0;t<e.length;t++){var o=e.charCodeAt(t);if(o<=255)n[s++]=o;else{n[s++]=o>>>8;n[s++]=255&o}}}else if(e instanceof Uint8Array){n=e;s=n.length}else{if(!("object"==typeof e&&"length"in e))throw new Error("Wrong data format in MurmurHash3_64_update. Input must be a string or array.");n=e;s=n.length;r=!0}var c=s>>2,l=s-4*c,h=r?new i(n,c):new Uint32Array(n.buffer,0,c),u=0,f=0,d=this.h1,g=this.h2,p=3432918353,m=461845907;for(t=0;t<c;t++)if(1&t){u=h[t];u=u*p&4294901760|11601*u&65535;u=u<<15|u>>>17;u=u*m&4294901760|13715*u&65535;d^=u;d=d<<13|d>>>19;d=5*d+3864292196}else{f=h[t];f=f*p&4294901760|11601*f&65535;f=f<<15|f>>>17;f=f*m&4294901760|13715*f&65535;g^=f;g=g<<13|g>>>19;g=5*g+3864292196}u=0;switch(l){case 3:u^=n[4*c+2]<<16;case 2:u^=n[4*c+1]<<8;case 1:u^=n[4*c];u=u*p&4294901760|11601*u&65535;u=u<<15|u>>>17;u=u*m&4294901760|13715*u&65535;1&c?d^=u:g^=u}this.h1=d;this.h2=g;return this},hexdigest:function(){var e=this.h1,t=this.h2;e^=t>>>1;e=3981806797*e&4294901760|36045*e&65535;t=4283543511*t&4294901760|(2950163797*(t<<16|e>>>16)&4294901760)>>>16;e^=t>>>1;e=444984403*e&4294901760|60499*e&65535;t=3301882366*t&4294901760|(3120437893*(t<<16|e>>>16)&4294901760)>>>16;e^=t>>>1;for(var a=0,r=[e,t],i="";a<r.length;a++){for(var n=(r[a]>>>0).toString(16);n.length<8;)n="0"+n;i+=n}return i}};return t}();t.MurmurHash3_64=n},function(e,t,a){"use strict";function r(e,t,a){return["TilingPattern",a,e,t.getArray("Matrix"),t.getArray("BBox"),t.get("XStep"),t.get("YStep"),t.get("PaintType"),t.get("TilingType")]}var i=a(0),n=a(1),s=a(6),o=a(3),c=i.UNSUPPORTED_FEATURES,l=i.MissingDataException,h=i.Util,u=i.assert,f=i.error,d=i.info,g=i.warn,p=n.isStream,m=s.PDFFunction,b=o.ColorSpace,v={FUNCTION_BASED:1,AXIAL:2,RADIAL:3,FREE_FORM_MESH:4,LATTICE_FORM_MESH:5,COONS_PATCH_MESH:6,TENSOR_PATCH_MESH:7},y=function(){function e(){f("should not call Pattern constructor")}e.prototype={getPattern:function(e){f("Should not call Pattern.getStyle: "+e)}};e.parseShading=function(e,t,a,r,i){var n=p(e)?e.dict:e,s=n.get("ShadingType");try{switch(s){case v.AXIAL:case v.RADIAL:return new k.RadialAxial(n,t,a,r);case v.FREE_FORM_MESH:case v.LATTICE_FORM_MESH:case v.COONS_PATCH_MESH:case v.TENSOR_PATCH_MESH:return new k.Mesh(e,t,a,r);default:throw new Error("Unsupported ShadingType: "+s)}}catch(e){if(e instanceof l)throw e;i.send("UnsupportedFeature",{featureId:c.shadingPattern});g(e);return new k.Dummy}};return e}(),k={};k.SMALL_NUMBER=1e-6;k.RadialAxial=function(){function e(e,t,a,r){this.matrix=t;this.coordsArr=e.getArray("Coords");this.shadingType=e.get("ShadingType");this.type="Pattern";var i=e.get("ColorSpace","CS");i=b.parse(i,a,r);this.cs=i;var n=0,s=1;if(e.has("Domain")){var o=e.getArray("Domain");n=o[0];s=o[1]}var c=!1,l=!1;if(e.has("Extend")){var u=e.getArray("Extend");c=u[0];l=u[1]}if(!(this.shadingType!==v.RADIAL||c&&l)){var f=this.coordsArr[0],p=this.coordsArr[1],y=this.coordsArr[2],w=this.coordsArr[3],C=this.coordsArr[4],x=this.coordsArr[5],S=Math.sqrt((f-w)*(f-w)+(p-C)*(p-C));y<=x+S&&x<=y+S&&g("Unsupported radial gradient.")}this.extendStart=c;this.extendEnd=l;var A=e.get("Function"),I=m.parseArray(a,A),B=s-n,R=B/10,T=this.colorStops=[];if(n>=s||R<=0)d("Bad shading domain.");else{for(var O,P=new Float32Array(i.numComps),M=new Float32Array(1),E=n;E<=s;E+=R){M[0]=E;I(M,0,P,0);O=i.getRgb(P,0);var L=h.makeCssRgb(O[0],O[1],O[2]);T.push([(E-n)/B,L])}var D="transparent";if(e.has("Background")){O=i.getRgb(e.get("Background"),0);D=h.makeCssRgb(O[0],O[1],O[2])}if(!c){T.unshift([0,D]);T[1][0]+=k.SMALL_NUMBER}if(!l){T[T.length-1][0]-=k.SMALL_NUMBER;T.push([1,D])}this.colorStops=T}}e.prototype={getIR:function(){var e,t,a,r,i,n=this.coordsArr,s=this.shadingType;if(s===v.AXIAL){t=[n[0],n[1]];a=[n[2],n[3]];r=null;i=null;e="axial"}else if(s===v.RADIAL){t=[n[0],n[1]];a=[n[3],n[4]];r=n[2];i=n[5];e="radial"}else f("getPattern type unknown: "+s);var o=this.matrix;if(o){t=h.applyTransform(t,o);a=h.applyTransform(a,o);if(s===v.RADIAL){var c=h.singularValueDecompose2dScale(o);r*=c[0];i*=c[1]}}return["RadialAxial",e,this.colorStops,t,a,r,i]}};return e}();k.Mesh=function(){function e(e,t){this.stream=e;this.context=t;this.buffer=0;this.bufferLength=0;var a=t.numComps;this.tmpCompsBuf=new Float32Array(a);var r=t.colorSpace.numComps;this.tmpCsCompsBuf=t.colorFn?new Float32Array(r):this.tmpCompsBuf}function t(e,t){for(var a=e.coords,r=e.colors,i=[],n=[],s=0;t.hasData;){var o=t.readFlag(),c=t.readCoordinate(),l=t.readComponents();if(0===s){u(0<=o&&o<=2,"Unknown type4 flag");switch(o){case 0:s=3;break;case 1:n.push(n[n.length-2],n[n.length-1]);s=1;break;case 2:n.push(n[n.length-3],n[n.length-1]);s=1}i.push(o)}n.push(a.length);a.push(c);r.push(l);s--;t.align()}e.figures.push({type:"triangles",coords:new Int32Array(n),colors:new Int32Array(n)})}function a(e,t,a){for(var r=e.coords,i=e.colors,n=[];t.hasData;){var s=t.readCoordinate(),o=t.readComponents();n.push(r.length);r.push(s);i.push(o)}e.figures.push({type:"lattice",coords:new Int32Array(n),colors:new Int32Array(n),verticesPerRow:a})}function r(e,t){var a=e.figures[t];u("patch"===a.type,"Unexpected patch mesh figure");var r=e.coords,i=e.colors,n=a.coords,s=a.colors,o=Math.min(r[n[0]][0],r[n[3]][0],r[n[12]][0],r[n[15]][0]),c=Math.min(r[n[0]][1],r[n[3]][1],r[n[12]][1],r[n[15]][1]),f=Math.max(r[n[0]][0],r[n[3]][0],r[n[12]][0],r[n[15]][0]),p=Math.max(r[n[0]][1],r[n[3]][1],r[n[12]][1],r[n[15]][1]),m=Math.ceil((f-o)*d/(e.bounds[2]-e.bounds[0]));m=Math.max(l,Math.min(h,m));var b=Math.ceil((p-c)*d/(e.bounds[3]-e.bounds[1]));b=Math.max(l,Math.min(h,b));for(var v=m+1,y=new Int32Array((b+1)*v),k=new Int32Array((b+1)*v),w=0,C=new Uint8Array(3),x=new Uint8Array(3),S=i[s[0]],A=i[s[1]],I=i[s[2]],B=i[s[3]],R=g(b),T=g(m),O=0;O<=b;O++){C[0]=(S[0]*(b-O)+I[0]*O)/b|0;C[1]=(S[1]*(b-O)+I[1]*O)/b|0;C[2]=(S[2]*(b-O)+I[2]*O)/b|0;x[0]=(A[0]*(b-O)+B[0]*O)/b|0;x[1]=(A[1]*(b-O)+B[1]*O)/b|0;x[2]=(A[2]*(b-O)+B[2]*O)/b|0;for(var P=0;P<=m;P++,w++)if(0!==O&&O!==b||0!==P&&P!==m){for(var M=0,E=0,L=0,D=0;D<=3;D++)for(var F=0;F<=3;F++,L++){var q=R[O][D]*T[P][F];M+=r[n[L]][0]*q;E+=r[n[L]][1]*q}y[w]=r.length;r.push([M,E]);k[w]=i.length;var U=new Uint8Array(3);U[0]=(C[0]*(m-P)+x[0]*P)/m|0;U[1]=(C[1]*(m-P)+x[1]*P)/m|0;U[2]=(C[2]*(m-P)+x[2]*P)/m|0;i.push(U)}}y[0]=n[0];k[0]=s[0];y[m]=n[3];k[m]=s[1];y[v*b]=n[12];k[v*b]=s[2];y[v*b+m]=n[15];k[v*b+m]=s[3];e.figures[t]={type:"lattice",coords:y,colors:k,verticesPerRow:v}}function i(e,t){for(var a=e.coords,r=e.colors,i=new Int32Array(16),n=new Int32Array(4);t.hasData;){var s=t.readFlag();u(0<=s&&s<=3,"Unknown type6 flag");var o,c,l=a.length;for(o=0,c=0!==s?8:12;o<c;o++)a.push(t.readCoordinate());var h=r.length;for(o=0,c=0!==s?2:4;o<c;o++)r.push(t.readComponents());var f,d,g,p;switch(s){case 0:i[12]=l+3;i[13]=l+4;i[14]=l+5;i[15]=l+6;i[8]=l+2;i[11]=l+7;i[4]=l+1;i[7]=l+8;i[0]=l;i[1]=l+11;i[2]=l+10;i[3]=l+9;n[2]=h+1;n[3]=h+2;n[0]=h;n[1]=h+3;break;case 1:f=i[12];d=i[13];g=i[14];p=i[15];i[12]=p;i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=g;i[11]=l+3;i[4]=d;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[2];d=n[3];n[2]=d;n[3]=h;n[0]=f;n[1]=h+1;break;case 2:f=i[15];d=i[11];i[12]=i[3];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[7];i[11]=l+3;i[4]=d;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[3];n[2]=n[1];n[3]=h;n[0]=f;n[1]=h+1;break;case 3:i[12]=i[0];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[1];i[11]=l+3;i[4]=i[2];i[7]=l+4;i[0]=i[3];i[1]=l+7;i[2]=l+6;i[3]=l+5;n[2]=n[0];n[3]=h;n[0]=n[1];n[1]=h+1}i[5]=a.length;a.push([(-4*a[i[0]][0]-a[i[15]][0]+6*(a[i[4]][0]+a[i[1]][0])-2*(a[i[12]][0]+a[i[3]][0])+3*(a[i[13]][0]+a[i[7]][0]))/9,(-4*a[i[0]][1]-a[i[15]][1]+6*(a[i[4]][1]+a[i[1]][1])-2*(a[i[12]][1]+a[i[3]][1])+3*(a[i[13]][1]+a[i[7]][1]))/9]);i[6]=a.length;a.push([(-4*a[i[3]][0]-a[i[12]][0]+6*(a[i[2]][0]+a[i[7]][0])-2*(a[i[0]][0]+a[i[15]][0])+3*(a[i[4]][0]+a[i[14]][0]))/9,(-4*a[i[3]][1]-a[i[12]][1]+6*(a[i[2]][1]+a[i[7]][1])-2*(a[i[0]][1]+a[i[15]][1])+3*(a[i[4]][1]+a[i[14]][1]))/9]);i[9]=a.length;a.push([(-4*a[i[12]][0]-a[i[3]][0]+6*(a[i[8]][0]+a[i[13]][0])-2*(a[i[0]][0]+a[i[15]][0])+3*(a[i[11]][0]+a[i[1]][0]))/9,(-4*a[i[12]][1]-a[i[3]][1]+6*(a[i[8]][1]+a[i[13]][1])-2*(a[i[0]][1]+a[i[15]][1])+3*(a[i[11]][1]+a[i[1]][1]))/9]);i[10]=a.length;a.push([(-4*a[i[15]][0]-a[i[0]][0]+6*(a[i[11]][0]+a[i[14]][0])-2*(a[i[12]][0]+a[i[3]][0])+3*(a[i[2]][0]+a[i[8]][0]))/9,(-4*a[i[15]][1]-a[i[0]][1]+6*(a[i[11]][1]+a[i[14]][1])-2*(a[i[12]][1]+a[i[3]][1])+3*(a[i[2]][1]+a[i[8]][1]))/9]);e.figures.push({type:"patch",coords:new Int32Array(i),colors:new Int32Array(n)})}}function n(e,t){for(var a=e.coords,r=e.colors,i=new Int32Array(16),n=new Int32Array(4);t.hasData;){var s=t.readFlag();u(0<=s&&s<=3,"Unknown type7 flag");var o,c,l=a.length;for(o=0,c=0!==s?12:16;o<c;o++)a.push(t.readCoordinate());var h=r.length;for(o=0,c=0!==s?2:4;o<c;o++)r.push(t.readComponents());var f,d,g,p;switch(s){case 0:i[12]=l+3;i[13]=l+4;i[14]=l+5;i[15]=l+6;i[8]=l+2;i[9]=l+13;i[10]=l+14;i[11]=l+7;i[4]=l+1;i[5]=l+12;i[6]=l+15;i[7]=l+8;i[0]=l;i[1]=l+11;i[2]=l+10;i[3]=l+9;n[2]=h+1;n[3]=h+2;n[0]=h;n[1]=h+3;break;case 1:f=i[12];d=i[13];g=i[14];p=i[15];i[12]=p;i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=g;i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=d;i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[2];d=n[3];n[2]=d;n[3]=h;n[0]=f;n[1]=h+1;break;case 2:f=i[15];d=i[11];i[12]=i[3];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[7];i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=d;i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[3];n[2]=n[1];n[3]=h;n[0]=f;n[1]=h+1;break;case 3:i[12]=i[0];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[1];i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=i[2];i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=i[3];i[1]=l+7;i[2]=l+6;i[3]=l+5;n[2]=n[0];n[3]=h;n[0]=n[1];n[1]=h+1}e.figures.push({type:"patch",coords:new Int32Array(i),colors:new Int32Array(n)})}}function s(e){for(var t=e.coords[0][0],a=e.coords[0][1],r=t,i=a,n=1,s=e.coords.length;n<s;n++){var o=e.coords[n][0],c=e.coords[n][1];t=t>o?o:t;a=a>c?c:a;r=r<o?o:r;i=i<c?c:i}e.bounds=[t,a,r,i]}function o(e){var t,a,r,i,n=e.coords,s=new Float32Array(2*n.length);for(t=0,r=0,a=n.length;t<a;t++){var o=n[t];s[r++]=o[0];s[r++]=o[1]}e.coords=s;var c=e.colors,l=new Uint8Array(3*c.length);for(t=0,r=0,a=c.length;t<a;t++){var h=c[t];l[r++]=h[0];l[r++]=h[1];l[r++]=h[2]}e.colors=l;var u=e.figures;for(t=0,a=u.length;t<a;t++){var f=u[t],d=f.coords,g=f.colors;for(r=0,i=d.length;r<i;r++){d[r]*=2;g[r]*=3}}}function c(c,l,h,d){u(p(c),"Mesh data is not a stream");var g=c.dict;this.matrix=l;this.shadingType=g.get("ShadingType");this.type="Pattern";this.bbox=g.getArray("BBox");var y=g.get("ColorSpace","CS");y=b.parse(y,h,d);this.cs=y;this.background=g.has("Background")?y.getRgb(g.get("Background"),0):null;var k=g.get("Function"),w=k?m.parseArray(h,k):null;this.coords=[];this.colors=[];this.figures=[];var C={bitsPerCoordinate:g.get("BitsPerCoordinate"),bitsPerComponent:g.get("BitsPerComponent"),bitsPerFlag:g.get("BitsPerFlag"),decode:g.getArray("Decode"),colorFn:w,colorSpace:y,numComps:w?1:y.numComps},x=new e(c,C),S=!1;switch(this.shadingType){case v.FREE_FORM_MESH:t(this,x);break;case v.LATTICE_FORM_MESH:var A=0|g.get("VerticesPerRow");u(A>=2,"Invalid VerticesPerRow");a(this,x,A);break;case v.COONS_PATCH_MESH:i(this,x);S=!0;break;case v.TENSOR_PATCH_MESH:n(this,x);S=!0;break;default:f("Unsupported mesh type.")}if(S){s(this);for(var I=0,B=this.figures.length;I<B;I++)r(this,I)}s(this);o(this)}e.prototype={get hasData(){if(this.stream.end)return this.stream.pos<this.stream.end;if(this.bufferLength>0)return!0;var e=this.stream.getByte();if(e<0)return!1;this.buffer=e;this.bufferLength=8;return!0},readBits:function(e){var t=this.buffer,a=this.bufferLength;if(32===e){if(0===a)return(this.stream.getByte()<<24|this.stream.getByte()<<16|this.stream.getByte()<<8|this.stream.getByte())>>>0;t=t<<24|this.stream.getByte()<<16|this.stream.getByte()<<8|this.stream.getByte();var r=this.stream.getByte();this.buffer=r&(1<<a)-1;return(t<<8-a|(255&r)>>a)>>>0}if(8===e&&0===a)return this.stream.getByte();for(;a<e;){t=t<<8|this.stream.getByte();a+=8}a-=e;this.bufferLength=a;this.buffer=t&(1<<a)-1;return t>>a},align:function(){this.buffer=0;this.bufferLength=0},readFlag:function(){return this.readBits(this.context.bitsPerFlag)},readCoordinate:function(){var e=this.context.bitsPerCoordinate,t=this.readBits(e),a=this.readBits(e),r=this.context.decode,i=e<32?1/((1<<e)-1):2.3283064365386963e-10;return[t*i*(r[1]-r[0])+r[0],a*i*(r[3]-r[2])+r[2]]},readComponents:function(){for(var e=this.context.numComps,t=this.context.bitsPerComponent,a=t<32?1/((1<<t)-1):2.3283064365386963e-10,r=this.context.decode,i=this.tmpCompsBuf,n=0,s=4;n<e;n++,s+=2){var o=this.readBits(t);i[n]=o*a*(r[s+1]-r[s])+r[s]}var c=this.tmpCsCompsBuf;this.context.colorFn&&this.context.colorFn(i,0,c,0);return this.context.colorSpace.getRgb(c,0)}};var l=3,h=20,d=20,g=function(){function e(e){for(var t=[],a=0;a<=e;a++){var r=a/e,i=1-r;t.push(new Float32Array([i*i*i,3*r*i*i,3*r*r*i,r*r*r]))}return t}var t=[];return function(a){t[a]||(t[a]=e(a));return t[a]}}();c.prototype={getIR:function(){return["Mesh",this.shadingType,this.coords,this.colors,this.figures,this.bounds,this.matrix,this.bbox,this.background]}};return c}();k.Dummy=function(){function e(){this.type="Pattern"}e.prototype={getIR:function(){return["Dummy"]}};return e}();t.Pattern=y;t.getTilingPatternIR=r},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(12),s=a(24),o=r.warn,c=r.createValidAbsoluteUrl,l=r.shadow,h=r.NotImplementedException,u=r.MissingDataException,f=r.createPromiseCapability,d=r.Util,g=i.Stream,p=n.ChunkedStreamManager,m=s.PDFDocument,b=function(){function e(){throw new Error("Cannot initialize BaseManagerManager")}e.prototype={get docId(){return this._docId},get password(){return this._password},get docBaseUrl(){var e=null;if(this._docBaseUrl){var t=c(this._docBaseUrl);t?e=t.href:o('Invalid absolute docBaseUrl: "'+this._docBaseUrl+'".')}return l(this,"docBaseUrl",e)},onLoadedStream:function(){throw new h},ensureDoc:function(e,t){return this.ensure(this.pdfDocument,e,t)},ensureXRef:function(e,t){return this.ensure(this.pdfDocument.xref,e,t)},ensureCatalog:function(e,t){return this.ensure(this.pdfDocument.catalog,e,t)},getPage:function(e){return this.pdfDocument.getPage(e)},cleanup:function(){return this.pdfDocument.cleanup()},ensure:function(e,t,a){return new h},requestRange:function(e,t){return new h},requestLoadedStream:function(){return new h},sendProgressiveData:function(e){return new h},updatePassword:function(e){this._password=e},terminate:function(){return new h}};return e}(),v=function(){function e(e,t,a,r,i){this._docId=e;this._password=a;this._docBaseUrl=i;this.evaluatorOptions=r;var n=new g(t);this.pdfDocument=new m(this,n);this._loadedStreamCapability=f();this._loadedStreamCapability.resolve(n)}d.inherit(e,b,{ensure:function(e,t,a){return new Promise(function(r,i){try{var n,s=e[t];n="function"==typeof s?s.apply(e,a):s;r(n)}catch(e){i(e)}})},requestRange:function(e,t){return Promise.resolve()},requestLoadedStream:function(){},onLoadedStream:function(){return this._loadedStreamCapability.promise},terminate:function(){}});return e}(),y=function(){function e(e,t,a,r,i){this._docId=e;this._password=a.password;this._docBaseUrl=i;this.msgHandler=a.msgHandler;this.evaluatorOptions=r;var n={msgHandler:a.msgHandler,url:a.url,length:a.length,disableAutoFetch:a.disableAutoFetch,rangeChunkSize:a.rangeChunkSize};this.streamManager=new p(t,n);this.pdfDocument=new m(this,this.streamManager.getStream())}d.inherit(e,b,{ensure:function(e,t,a){var r=this;return new Promise(function(i,n){function s(){try{var o,c=e[t];o="function"==typeof c?c.apply(e,a):c;i(o)}catch(e){if(!(e instanceof u)){n(e);return}r.streamManager.requestRange(e.begin,e.end).then(s,n)}}s()})},requestRange:function(e,t){return this.streamManager.requestRange(e,t)},requestLoadedStream:function(){this.streamManager.requestAllChunks()},sendProgressiveData:function(e){this.streamManager.onReceiveData({chunk:e})},onLoadedStream:function(){return this.streamManager.onLoadedStream()},terminate:function(){this.streamManager.abort()}});return e}();t.LocalPdfManager=v;t.NetworkPdfManager=y},function(e,t,a){"use strict";var r=a(0),i=a(1),n=r.error,s=r.isSpace,o=i.EOF,c=function(){function e(e){this.lexer=e;this.operators=[];this.token=null;this.prev=null}e.prototype={nextToken:function(){this.prev=this.token;this.token=this.lexer.getToken()},accept:function(e){if(this.token.type===e){this.nextToken();return!0}return!1},expect:function(e){if(this.accept(e))return!0;n("Unexpected symbol: found "+this.token.type+" expected "+e+".")},parse:function(){this.nextToken();this.expect(l.LBRACE);this.parseBlock();this.expect(l.RBRACE);return this.operators},parseBlock:function(){for(;;)if(this.accept(l.NUMBER))this.operators.push(this.prev.value);else if(this.accept(l.OPERATOR))this.operators.push(this.prev.value);else{if(!this.accept(l.LBRACE))return;this.parseCondition()}},parseCondition:function(){var e=this.operators.length;this.operators.push(null,null);this.parseBlock();this.expect(l.RBRACE);if(this.accept(l.IF)){this.operators[e]=this.operators.length;this.operators[e+1]="jz"}else if(this.accept(l.LBRACE)){var t=this.operators.length;this.operators.push(null,null);var a=this.operators.length;this.parseBlock();this.expect(l.RBRACE);this.expect(l.IFELSE);this.operators[t]=this.operators.length;this.operators[t+1]="j";this.operators[e]=a;this.operators[e+1]="jz"}else n("PS Function: error parsing conditional.")}};return e}(),l={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3,IF:4, +IFELSE:5},h=function(){function e(e,t){this.type=e;this.value=t}var t=Object.create(null);e.getOperator=function(a){var r=t[a];return r||(t[a]=new e(l.OPERATOR,a))};e.LBRACE=new e(l.LBRACE,"{");e.RBRACE=new e(l.RBRACE,"}");e.IF=new e(l.IF,"IF");e.IFELSE=new e(l.IFELSE,"IFELSE");return e}(),u=function(){function e(e){this.stream=e;this.nextChar();this.strBuf=[]}e.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var e=!1,t=this.currentChar;;){if(t<0)return o;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!s(t))break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new h(l.NUMBER,this.getNumber());case 123:this.nextChar();return h.LBRACE;case 125:this.nextChar();return h.RBRACE}var a=this.strBuf;a.length=0;a[0]=String.fromCharCode(t);for(;(t=this.nextChar())>=0&&(t>=65&&t<=90||t>=97&&t<=122);)a.push(String.fromCharCode(t));var r=a.join("");switch(r.toLowerCase()){case"if":return h.IF;case"ifelse":return h.IFELSE;default:return h.getOperator(r)}},getNumber:function(){var e=this.currentChar,t=this.strBuf;t.length=0;t[0]=String.fromCharCode(e);for(;(e=this.nextChar())>=0&&(e>=48&&e<=57||45===e||46===e);)t.push(String.fromCharCode(e));var a=parseFloat(t.join(""));isNaN(a)&&n("Invalid floating point number: "+a);return a}};return e}();t.PostScriptLexer=u;t.PostScriptParser=c},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(4),s=r.warn,o=r.isSpace,c=i.Stream,l=n.getEncoding,h=function(){function e(){this.width=0;this.lsb=0;this.flexing=!1;this.output=[];this.stack=[]}var t={hstem:[1],vstem:[3],vmoveto:[4],rlineto:[5],hlineto:[6],vlineto:[7],rrcurveto:[8],callsubr:[10],flex:[12,35],drop:[12,18],endchar:[14],rmoveto:[21],hmoveto:[22],vhcurveto:[30],hvcurveto:[31]};e.prototype={convert:function(e,a,r){for(var i,n,o,c=e.length,l=!1,h=0;h<c;h++){var u=e[h];if(u<32){12===u&&(u=(u<<8)+e[++h]);switch(u){case 1:case 3:this.stack=[];break;case 4:if(this.flexing){if(this.stack.length<1){l=!0;break}var f=this.stack.pop();this.stack.push(0,f);break}l=this.executeCommand(1,t.vmoveto);break;case 5:l=this.executeCommand(2,t.rlineto);break;case 6:l=this.executeCommand(1,t.hlineto);break;case 7:l=this.executeCommand(1,t.vlineto);break;case 8:l=this.executeCommand(6,t.rrcurveto);break;case 9:this.stack=[];break;case 10:if(this.stack.length<1){l=!0;break}o=this.stack.pop();l=this.convert(a[o],a,r);break;case 11:return l;case 13:if(this.stack.length<2){l=!0;break}i=this.stack.pop();n=this.stack.pop();this.lsb=n;this.width=i;this.stack.push(i,n);l=this.executeCommand(2,t.hmoveto);break;case 14:this.output.push(t.endchar[0]);break;case 21:if(this.flexing)break;l=this.executeCommand(2,t.rmoveto);break;case 22:if(this.flexing){this.stack.push(0);break}l=this.executeCommand(1,t.hmoveto);break;case 30:l=this.executeCommand(4,t.vhcurveto);break;case 31:l=this.executeCommand(4,t.hvcurveto);break;case 3072:case 3073:case 3074:this.stack=[];break;case 3078:if(r){this.seac=this.stack.splice(-4,4);l=this.executeCommand(0,t.endchar)}else l=this.executeCommand(4,t.endchar);break;case 3079:if(this.stack.length<4){l=!0;break}this.stack.pop();i=this.stack.pop();var d=this.stack.pop();n=this.stack.pop();this.lsb=n;this.width=i;this.stack.push(i,n,d);l=this.executeCommand(3,t.rmoveto);break;case 3084:if(this.stack.length<2){l=!0;break}var g=this.stack.pop(),p=this.stack.pop();this.stack.push(p/g);break;case 3088:if(this.stack.length<2){l=!0;break}o=this.stack.pop();var m=this.stack.pop();if(0===o&&3===m){var b=this.stack.splice(this.stack.length-17,17);this.stack.push(b[2]+b[0],b[3]+b[1],b[4],b[5],b[6],b[7],b[8],b[9],b[10],b[11],b[12],b[13],b[14]);l=this.executeCommand(13,t.flex,!0);this.flexing=!1;this.stack.push(b[15],b[16])}else 1===o&&0===m&&(this.flexing=!0);break;case 3089:break;case 3105:this.stack=[];break;default:s('Unknown type 1 charstring command of "'+u+'"')}if(l)break}else{u<=246?u-=139:u=u<=250?256*(u-247)+e[++h]+108:u<=254?-256*(u-251)-e[++h]-108:(255&e[++h])<<24|(255&e[++h])<<16|(255&e[++h])<<8|(255&e[++h])<<0;this.stack.push(u)}}return l},executeCommand:function(e,t,a){var r=this.stack.length;if(e>r)return!0;for(var i=r-e,n=i;n<r;n++){var s=this.stack[n];if(s===(0|s))this.output.push(28,s>>8&255,255&s);else{s=65536*s|0;this.output.push(255,s>>24&255,s>>16&255,s>>8&255,255&s)}}this.output.push.apply(this.output,t);a?this.stack.splice(i,e):this.stack.length=0;return!1}};return e}(),u=function(){function e(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function t(e,t,a){if(a>=e.length)return new Uint8Array(0);var r,i,n=0|t;for(r=0;r<a;r++)n=52845*(e[r]+n)+22719&65535;var s=e.length-a,o=new Uint8Array(s);for(r=a,i=0;i<s;r++,i++){var c=e[r];o[i]=c^n>>8;n=52845*(c+n)+22719&65535}return o}function a(t,a,r){var i,n,s=0|a,o=t.length,c=o>>>1,l=new Uint8Array(c);for(i=0,n=0;i<o;i++){var h=t[i];if(e(h)){i++;for(var u;i<o&&!e(u=t[i]);)i++;if(i<o){var f=parseInt(String.fromCharCode(h,u),16);l[n++]=f^s>>8;s=52845*(f+s)+22719&65535}}}return Array.prototype.slice.call(l,r,n)}function r(e){return 47===e||91===e||93===e||123===e||125===e||40===e||41===e}function i(r,i,s){if(i){var o=r.getBytes(),l=!(e(o[0])&&e(o[1])&&e(o[2])&&e(o[3]));r=new c(l?t(o,n,4):a(o,n,4))}this.seacAnalysisEnabled=!!s;this.stream=r;this.nextChar()}var n=55665;i.prototype={readNumberArray:function(){this.getToken();for(var e=[];;){var t=this.getToken();if(null===t||"]"===t||"}"===t)break;e.push(parseFloat(t||0))}return e},readNumber:function(){var e=this.getToken();return parseFloat(e||0)},readInt:function(){var e=this.getToken();return 0|parseInt(e||0,10)},readBoolean:function(){return"true"===this.getToken()?1:0},nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var e=!1,t=this.currentChar;;){if(-1===t)return null;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!o(t))break;t=this.nextChar()}if(r(t)){this.nextChar();return String.fromCharCode(t)}var a="";do{a+=String.fromCharCode(t);t=this.nextChar()}while(t>=0&&!o(t)&&!r(t));return a},extractFontProgram:function(){var e=this.stream,a=[],r=[],i=Object.create(null);i.lenIV=4;for(var n,s,o,c,l,u={subrs:[],charstrings:[],properties:{privateData:i}};null!==(n=this.getToken());)if("/"===n){n=this.getToken();switch(n){case"CharStrings":this.getToken();this.getToken();this.getToken();this.getToken();for(;;){n=this.getToken();if(null===n||"end"===n)break;if("/"===n){var f=this.getToken();s=this.readInt();this.getToken();o=e.makeSubStream(e.pos,s);c=u.properties.privateData.lenIV;l=t(o.getBytes(),4330,c);e.skip(s);this.nextChar();n=this.getToken();"noaccess"===n&&this.getToken();r.push({glyph:f,encoded:l})}}break;case"Subrs":this.readInt();this.getToken();for(;"dup"===(n=this.getToken());){var d=this.readInt();s=this.readInt();this.getToken();o=e.makeSubStream(e.pos,s);c=u.properties.privateData.lenIV;l=t(o.getBytes(),4330,c);e.skip(s);this.nextChar();n=this.getToken();"noaccess"===n&&this.getToken();a[d]=l}break;case"BlueValues":case"OtherBlues":case"FamilyBlues":case"FamilyOtherBlues":var g=this.readNumberArray();(g.length>0&&g.length,1)||(u.properties.privateData[n]=g);break;case"StemSnapH":case"StemSnapV":u.properties.privateData[n]=this.readNumberArray();break;case"StdHW":case"StdVW":u.properties.privateData[n]=this.readNumberArray()[0];break;case"BlueShift":case"lenIV":case"BlueFuzz":case"BlueScale":case"LanguageGroup":case"ExpansionFactor":u.properties.privateData[n]=this.readNumber();break;case"ForceBold":u.properties.privateData[n]=this.readBoolean()}}for(var p=0;p<r.length;p++){f=r[p].glyph;l=r[p].encoded;var m=new h,b=m.convert(l,a,this.seacAnalysisEnabled),v=m.output;b&&(v=[14]);u.charstrings.push({glyphName:f,charstring:v,width:m.width,lsb:m.lsb,seac:m.seac})}return u},extractFontHeader:function(e){for(var t;null!==(t=this.getToken());)if("/"===t){t=this.getToken();switch(t){case"FontMatrix":var a=this.readNumberArray();e.fontMatrix=a;break;case"Encoding":var r,i=this.getToken();if(/^\d+$/.test(i)){r=[];var n=0|parseInt(i,10);this.getToken();for(var s=0;s<n;s++){t=this.getToken();for(;"dup"!==t&&"def"!==t;){t=this.getToken();if(null===t)return}if("def"===t)break;var o=this.readInt();this.getToken();var c=this.getToken();r[o]=c;this.getToken()}}else r=l(i);e.builtInEncoding=r;break;case"FontBBox":var h=this.readNumberArray();e.ascent=Math.max(h[3],h[1]);e.descent=Math.min(h[1],h[3]);e.ascentScaled=!0}}}};return i}();t.Type1Parser=u},function(e,t,a){"use strict";var r=a(8);a(19);t.WorkerMessageHandler=r.WorkerMessageHandler},function(e,t,a){"use strict";(function(e){if("undefined"==typeof PDFJS||!PDFJS.compatibilityChecked){var t="undefined"!=typeof window?window:void 0!==e?e:"undefined"!=typeof self?self:void 0,a="undefined"!=typeof navigator&&navigator.userAgent||"",r=/Android/.test(a),i=/Android\s[0-2][^\d]/.test(a),n=/Android\s[0-4][^\d]/.test(a),s=a.indexOf("Chrom")>=0,o=/Chrome\/(39|40)\./.test(a),c=a.indexOf("CriOS")>=0,l=a.indexOf("Trident")>=0,h=/\b(iPad|iPhone|iPod)(?=;)/.test(a),u=a.indexOf("Opera")>=0,f=/Safari\//.test(a)&&!/(Chrome\/|Android\s)/.test(a),d="object"==typeof window&&"object"==typeof document;"undefined"==typeof PDFJS&&(t.PDFJS={});PDFJS.compatibilityChecked=!0;!function(){function e(e,t){return new r(this.slice(e,t))}function a(e,t){arguments.length<2&&(t=0);for(var a=0,r=e.length;a<r;++a,++t)this[t]=255&e[a]}function r(t){var r,i,n;if("number"==typeof t){r=[];for(i=0;i<t;++i)r[i]=0}else if("slice"in t)r=t.slice(0);else{r=[];for(i=0,n=t.length;i<n;++i)r[i]=t[i]}r.subarray=e;r.buffer=r;r.byteLength=r.length;r.set=a;"object"==typeof t&&t.buffer&&(r.buffer=t.buffer);return r}if("undefined"==typeof Uint8Array){t.Uint8Array=r;t.Int8Array=r;t.Uint32Array=r;t.Int32Array=r;t.Uint16Array=r;t.Float32Array=r;t.Float64Array=r}else{if(void 0===Uint8Array.prototype.subarray){Uint8Array.prototype.subarray=function(e,t){return new Uint8Array(this.slice(e,t))};Float32Array.prototype.subarray=function(e,t){return new Float32Array(this.slice(e,t))}}"undefined"==typeof Float64Array&&(t.Float64Array=Float32Array)}}();!function(){t.URL||(t.URL=t.webkitURL)}();!function(){if(void 0!==Object.defineProperty){var e=!0;try{d&&Object.defineProperty(new Image,"id",{value:"test"});var t=function(){};t.prototype={get id(){}};Object.defineProperty(new t,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(t){e=!1}if(e)return}Object.defineProperty=function(e,t,a){delete e[t];"get"in a&&e.__defineGetter__(t,a.get);"set"in a&&e.__defineSetter__(t,a.set);if("value"in a){e.__defineSetter__(t,function(e){this.__defineGetter__(t,function(){return e});return e});e[t]=a.value}}}();!function(){if("undefined"!=typeof XMLHttpRequest){var e=XMLHttpRequest.prototype,t=new XMLHttpRequest;"overrideMimeType"in t||Object.defineProperty(e,"overrideMimeType",{value:function(e){}});if(!("responseType"in t)){Object.defineProperty(e,"responseType",{get:function(){return this._responseType||"text"},set:function(e){if("text"===e||"arraybuffer"===e){this._responseType=e;"arraybuffer"===e&&"function"==typeof this.overrideMimeType&&this.overrideMimeType("text/plain; charset=x-user-defined")}}});"undefined"==typeof VBArray?Object.defineProperty(e,"response",{get:function(){if("arraybuffer"!==this.responseType)return this.responseText;var e,t=this.responseText,a=t.length,r=new Uint8Array(a);for(e=0;e<a;++e)r[e]=255&t.charCodeAt(e);return r.buffer}}):Object.defineProperty(e,"response",{get:function(){return"arraybuffer"===this.responseType?new Uint8Array(new VBArray(this.responseBody).toArray()):this.responseText}})}}}();!function(){if(!("btoa"in t)){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.btoa=function(t){var a,r,i="";for(a=0,r=t.length;a<r;a+=3){var n=255&t.charCodeAt(a),s=255&t.charCodeAt(a+1),o=255&t.charCodeAt(a+2),c=n>>2,l=(3&n)<<4|s>>4,h=a+1<r?(15&s)<<2|o>>6:64,u=a+2<r?63&o:64;i+=e.charAt(c)+e.charAt(l)+e.charAt(h)+e.charAt(u)}return i}}}();!function(){if(!("atob"in t)){t.atob=function(e){e=e.replace(/=+$/,"");if(e.length%4==1)throw new Error("bad atob input");for(var t,a,r=0,i=0,n="";a=e.charAt(i++);~a&&(t=r%4?64*t+a:a,r++%4)?n+=String.fromCharCode(255&t>>(-2*r&6)):0)a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a);return n}}}();!function(){void 0===Function.prototype.bind&&(Function.prototype.bind=function(e){var t=this,a=Array.prototype.slice.call(arguments,1);return function(){var r=a.concat(Array.prototype.slice.call(arguments));return t.apply(e,r)}})}();!function(){if(d){"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var e={},t=0,a=this.attributes.length;t<a;t++){var r=this.attributes[t];if("data-"===r.name.substring(0,5)){e[r.name.substring(5).replace(/\-([a-z])/g,function(e,t){return t.toUpperCase()})]=r.value}}Object.defineProperty(this,"_dataset",{value:e,writable:!1,enumerable:!1});return e},enumerable:!0})}}();!function(){function e(e,t,a,r){var i=e.className||"",n=i.split(/\s+/g);""===n[0]&&n.shift();var s=n.indexOf(t);s<0&&a&&n.push(t);s>=0&&r&&n.splice(s,1);e.className=n.join(" ");return s>=0}if(d){if(!("classList"in document.createElement("div"))){var t={add:function(t){e(this.element,t,!0,!1)},contains:function(t){return e(this.element,t,!1,!1)},remove:function(t){e(this.element,t,!1,!0)},toggle:function(t){e(this.element,t,!0,!0)}};Object.defineProperty(HTMLElement.prototype,"classList",{get:function(){if(this._classList)return this._classList;var e=Object.create(t,{element:{value:this,writable:!1,enumerable:!0}});Object.defineProperty(this,"_classList",{value:e,writable:!1,enumerable:!1});return e},enumerable:!0})}}}();!function(){if(!("undefined"==typeof importScripts||"console"in t)){var e={},a={log:function(){var e=Array.prototype.slice.call(arguments);t.postMessage({targetName:"main",action:"console_log",data:e})},error:function(){var e=Array.prototype.slice.call(arguments);t.postMessage({targetName:"main",action:"console_error",data:e})},time:function(t){e[t]=Date.now()},timeEnd:function(t){var a=e[t];if(!a)throw new Error("Unknown timer name "+t);this.log("Timer:",t,Date.now()-a)}};t.console=a}}();!function(){if(d)if("console"in window)if("bind"in console.log);else{console.log=function(e){return function(t){return e(t)}}(console.log);console.error=function(e){return function(t){return e(t)}}(console.error);console.warn=function(e){return function(t){return e(t)}}(console.warn)}else window.console={log:function(){},error:function(){},warn:function(){}}}();!function(){function e(e){t(e.target)&&e.stopPropagation()}function t(e){return e.disabled||e.parentNode&&t(e.parentNode)}u&&document.addEventListener("click",e,!0)}();!function(){(l||c)&&(PDFJS.disableCreateObjectURL=!0)}();!function(){"undefined"!=typeof navigator&&("language"in navigator||(PDFJS.locale=navigator.userLanguage||"en-US"))}();!function(){if(f||i||o||h){PDFJS.disableRange=!0;PDFJS.disableStream=!0}}();!function(){d&&(history.pushState&&!i||(PDFJS.disableHistory=!0))}();!function(){if(d)if(window.CanvasPixelArray)"function"!=typeof window.CanvasPixelArray.prototype.set&&(window.CanvasPixelArray.prototype.set=function(e){for(var t=0,a=this.length;t<a;t++)this[t]=e[t]});else{var e,t=!1;if(s){e=a.match(/Chrom(e|ium)\/([0-9]+)\./);t=e&&parseInt(e[2])<21}else if(r)t=n;else if(f){e=a.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);t=e&&parseInt(e[1])<6}if(t){var i=window.CanvasRenderingContext2D.prototype,o=i.createImageData;i.createImageData=function(e,t){var a=o.call(this,e,t);a.data.set=function(e){for(var t=0,a=this.length;t<a;t++)this[t]=e[t]};return a};i=null}}}();!function(){function e(){window.requestAnimationFrame=function(e){return window.setTimeout(e,20)};window.cancelAnimationFrame=function(e){window.clearTimeout(e)}}if(d)if(h)e();else if(!("requestAnimationFrame"in window)){window.requestAnimationFrame=window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame;"requestAnimationFrame"in window||e()}}();!function(){(h||r)&&(PDFJS.maxCanvasPixels=5242880)}();!function(){d&&l&&window.parent!==window&&(PDFJS.disableFullscreen=!0)}();!function(){d&&("currentScript"in document||Object.defineProperty(document,"currentScript",{get:function(){var e=document.getElementsByTagName("script");return e[e.length-1]},enumerable:!0,configurable:!0}))}();!function(){if(d){var e=document.createElement("input");try{e.type="number"}catch(r){var t=e.constructor.prototype,a=Object.getOwnPropertyDescriptor(t,"type");Object.defineProperty(t,"type",{get:function(){return a.get.call(this)},set:function(e){a.set.call(this,"number"===e?"text":e)},enumerable:!0,configurable:!0})}}}();!function(){if(d&&document.attachEvent){var e=document.constructor.prototype,t=Object.getOwnPropertyDescriptor(e,"readyState");Object.defineProperty(e,"readyState",{get:function(){var e=t.get.call(this);return"interactive"===e?"loading":e},set:function(e){t.set.call(this,e)},enumerable:!0,configurable:!0})}}();!function(){d&&void 0===Element.prototype.remove&&(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)})}();!function(){if(t.Promise){"function"!=typeof t.Promise.all&&(t.Promise.all=function(e){var a,r,i=0,n=[],s=new t.Promise(function(e,t){a=e;r=t});e.forEach(function(e,t){i++;e.then(function(e){n[t]=e;i--;0===i&&a(n)},r)});0===i&&a(n);return s});"function"!=typeof t.Promise.resolve&&(t.Promise.resolve=function(e){return new t.Promise(function(t){t(e)})});"function"!=typeof t.Promise.reject&&(t.Promise.reject=function(e){return new t.Promise(function(t,a){a(e)})});"function"!=typeof t.Promise.prototype.catch&&(t.Promise.prototype.catch=function(e){return t.Promise.prototype.then(void 0,e)})}else{var e=2,a={handlers:[],running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(e){if(0!==e._status){this.handlers=this.handlers.concat(e._handlers);e._handlers=[];if(!this.running){this.running=!0;setTimeout(this.runHandlers.bind(this),0)}}},runHandlers:function(){for(var t=Date.now()+1;this.handlers.length>0;){var a=this.handlers.shift(),r=a.thisPromise._status,i=a.thisPromise._value;try{if(1===r)"function"==typeof a.onResolve&&(i=a.onResolve(i));else if("function"==typeof a.onReject){i=a.onReject(i);r=1;a.thisPromise._unhandledRejection&&this.removeUnhandeledRejection(a.thisPromise)}}catch(t){r=e;i=t}a.nextPromise._updateStatus(r,i);if(Date.now()>=t)break}this.handlers.length>0?setTimeout(this.runHandlers.bind(this),0):this.running=!1},addUnhandledRejection:function(e){this.unhandledRejections.push({promise:e,time:Date.now()});this.scheduleRejectionCheck()},removeUnhandeledRejection:function(e){e._unhandledRejection=!1;for(var t=0;t<this.unhandledRejections.length;t++)if(this.unhandledRejections[t].promise===e){this.unhandledRejections.splice(t);t--}},scheduleRejectionCheck:function(){if(!this.pendingRejectionCheck){this.pendingRejectionCheck=!0;setTimeout(function(){this.pendingRejectionCheck=!1;for(var e=Date.now(),t=0;t<this.unhandledRejections.length;t++)if(e-this.unhandledRejections[t].time>500){var a=this.unhandledRejections[t].promise._value,r="Unhandled rejection: "+a;a.stack&&(r+="\n"+a.stack);try{throw new Error(r)}catch(e){console.warn(r)}this.unhandledRejections.splice(t);t--}this.unhandledRejections.length&&this.scheduleRejectionCheck()}.bind(this),500)}}},r=function(e){this._status=0;this._handlers=[];try{e.call(this,this._resolve.bind(this),this._reject.bind(this))}catch(e){this._reject(e)}};r.all=function(t){function a(t){if(s._status!==e){c=[];n(t)}}var i,n,s=new r(function(e,t){i=e;n=t}),o=t.length,c=[];if(0===o){i(c);return s}for(var l=0,h=t.length;l<h;++l){var u=t[l],f=function(t){return function(a){if(s._status!==e){c[t]=a;o--;0===o&&i(c)}}}(l);r.isPromise(u)?u.then(f,a):f(u)}return s};r.isPromise=function(e){return e&&"function"==typeof e.then};r.resolve=function(e){return new r(function(t){t(e)})};r.reject=function(e){return new r(function(t,a){a(e)})};r.prototype={_status:null,_value:null,_handlers:null,_unhandledRejection:null,_updateStatus:function(t,i){if(1!==this._status&&this._status!==e)if(1===t&&r.isPromise(i))i.then(this._updateStatus.bind(this,1),this._updateStatus.bind(this,e));else{this._status=t;this._value=i;if(t===e&&0===this._handlers.length){this._unhandledRejection=!0;a.addUnhandledRejection(this)}a.scheduleHandlers(this)}},_resolve:function(e){this._updateStatus(1,e)},_reject:function(t){this._updateStatus(e,t)},then:function(e,t){var i=new r(function(e,t){this.resolve=e;this.reject=t});this._handlers.push({thisPromise:this,onResolve:e,onReject:t,nextPromise:i});a.scheduleHandlers(this);return i},catch:function(e){return this.then(void 0,e)}};t.Promise=r}}();!function(){function e(){this.id="$weakmap"+a++}if(!t.WeakMap){var a=0;e.prototype={has:function(e){return!!Object.getOwnPropertyDescriptor(e,this.id)},get:function(e,t){return this.has(e)?e[this.id]:t},set:function(e,t){Object.defineProperty(e,this.id,{value:t,enumerable:!1,configurable:!0})},delete:function(e){delete e[this.id]}};t.WeakMap=e}}();!function(){function e(e){return void 0!==u[e]}function a(){o.call(this);this._isInvalid=!0}function r(e){""===e&&a.call(this);return e.toLowerCase()}function i(e){var t=e.charCodeAt(0);return t>32&&t<127&&-1===[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function n(e){var t=e.charCodeAt(0);return t>32&&t<127&&-1===[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function s(t,s,o){function c(e){y.push(e)}var l=s||"scheme start",h=0,m="",b=!1,v=!1,y=[];e:for(;(t[h-1]!==d||0===h)&&!this._isInvalid;){var k=t[h];switch(l){case"scheme start":if(!k||!g.test(k)){if(s){c("Invalid scheme.");break e}m="";l="no scheme";continue}m+=k.toLowerCase();l="scheme";break;case"scheme":if(k&&p.test(k))m+=k.toLowerCase();else{if(":"!==k){if(s){if(k===d)break e;c("Code point not allowed in scheme: "+k);break e}m="";h=0;l="no scheme";continue}this._scheme=m;m="";if(s)break e;e(this._scheme)&&(this._isRelative=!0);l="file"===this._scheme?"relative":this._isRelative&&o&&o._scheme===this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":if("?"===k){this._query="?";l="query"}else if("#"===k){this._fragment="#";l="fragment"}else k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._schemeData+=i(k));break;case"no scheme":if(o&&e(o._scheme)){l="relative";continue}c("Missing scheme.");a.call(this);break;case"relative or authority":if("/"!==k||"/"!==t[h+1]){c("Expected /, got: "+k);l="relative";continue}l="authority ignore slashes";break;case"relative":this._isRelative=!0;"file"!==this._scheme&&(this._scheme=o._scheme);if(k===d){this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query=o._query;this._username=o._username;this._password=o._password;break e}if("/"===k||"\\"===k){"\\"===k&&c("\\ is an invalid code point.");l="relative slash"}else if("?"===k){this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query="?";this._username=o._username;this._password=o._password;l="query"}else{if("#"!==k){var w=t[h+1],C=t[h+2];if("file"!==this._scheme||!g.test(k)||":"!==w&&"|"!==w||C!==d&&"/"!==C&&"\\"!==C&&"?"!==C&&"#"!==C){this._host=o._host;this._port=o._port;this._username=o._username;this._password=o._password;this._path=o._path.slice();this._path.pop()}l="relative path";continue}this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query=o._query;this._fragment="#";this._username=o._username;this._password=o._password;l="fragment"}break;case"relative slash":if("/"!==k&&"\\"!==k){if("file"!==this._scheme){this._host=o._host;this._port=o._port;this._username=o._username;this._password=o._password}l="relative path";continue}"\\"===k&&c("\\ is an invalid code point.");l="file"===this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!==k){c("Expected '/', got: "+k);l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":l="authority ignore slashes";if("/"!==k){c("Expected '/', got: "+k);continue}break;case"authority ignore slashes":if("/"!==k&&"\\"!==k){l="authority";continue}c("Expected authority, got: "+k);break;case"authority":if("@"===k){if(b){c("@ already seen.");m+="%40"}b=!0;for(var x=0;x<m.length;x++){var S=m[x];if("\t"!==S&&"\n"!==S&&"\r"!==S)if(":"!==S||null!==this._password){var A=i(S);null!==this._password?this._password+=A:this._username+=A}else this._password="";else c("Invalid whitespace in authority.")}m=""}else{if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){h-=m.length;m="";l="host";continue}m+=k}break;case"file host":if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){if(2!==m.length||!g.test(m[0])||":"!==m[1]&&"|"!==m[1])if(0===m.length)l="relative path start";else{this._host=r.call(this,m);m="";l="relative path start"}else l="relative path";continue}"\t"===k||"\n"===k||"\r"===k?c("Invalid whitespace in file host."):m+=k;break;case"host":case"hostname":if(":"!==k||v){if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){this._host=r.call(this,m);m="";l="relative path start";if(s)break e;continue}if("\t"!==k&&"\n"!==k&&"\r"!==k){"["===k?v=!0:"]"===k&&(v=!1);m+=k}else c("Invalid code point in host/hostname: "+k)}else{this._host=r.call(this,m);m="";l="port";if("hostname"===s)break e}break;case"port":if(/[0-9]/.test(k))m+=k;else{if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k||s){if(""!==m){var I=parseInt(m,10);I!==u[this._scheme]&&(this._port=I+"");m=""}if(s)break e;l="relative path start";continue}"\t"===k||"\n"===k||"\r"===k?c("Invalid code point in port: "+k):a.call(this)}break;case"relative path start":"\\"===k&&c("'\\' not allowed in path.");l="relative path";if("/"!==k&&"\\"!==k)continue;break;case"relative path":if(k!==d&&"/"!==k&&"\\"!==k&&(s||"?"!==k&&"#"!==k))"\t"!==k&&"\n"!==k&&"\r"!==k&&(m+=i(k));else{"\\"===k&&c("\\ not allowed in relative path.");var B;(B=f[m.toLowerCase()])&&(m=B);if(".."===m){this._path.pop();"/"!==k&&"\\"!==k&&this._path.push("")}else if("."===m&&"/"!==k&&"\\"!==k)this._path.push("");else if("."!==m){"file"===this._scheme&&0===this._path.length&&2===m.length&&g.test(m[0])&&"|"===m[1]&&(m=m[0]+":");this._path.push(m)}m="";if("?"===k){this._query="?";l="query"}else if("#"===k){this._fragment="#";l="fragment"}}break;case"query":if(s||"#"!==k)k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._query+=n(k));else{this._fragment="#";l="fragment"}break;case"fragment":k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._fragment+=k)}h++}}function o(){this._scheme="";this._schemeData="";this._username="";this._password=null;this._host="";this._port="";this._path=[];this._query="";this._fragment="";this._isInvalid=!1;this._isRelative=!1}function c(e,t){void 0===t||t instanceof c||(t=new c(String(t)));this._url=e;o.call(this);var a=e.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g,"");s.call(this,a,null,t)}var l=!1;try{if("function"==typeof URL&&"object"==typeof URL.prototype&&"origin"in URL.prototype){var h=new URL("b","http://a");h.pathname="c%20d";l="http://a/c%20d"===h.href}}catch(e){}if(!l){var u=Object.create(null);u.ftp=21;u.file=0;u.gopher=70;u.http=80;u.https=443;u.ws=80;u.wss=443;var f=Object.create(null);f["%2e"]=".";f[".%2e"]="..";f["%2e."]="..";f["%2e%2e"]="..";var d,g=/[a-zA-Z]/,p=/[a-zA-Z0-9\+\-\.]/;c.prototype={toString:function(){return this.href},get href(){if(this._isInvalid)return this._url;var e="";""===this._username&&null===this._password||(e=this._username+(null!==this._password?":"+this._password:"")+"@");return this.protocol+(this._isRelative?"//"+e+this.host:"")+this.pathname+this._query+this._fragment},set href(e){o.call(this);s.call(this,e)},get protocol(){return this._scheme+":"},set protocol(e){this._isInvalid||s.call(this,e+":","scheme start")},get host(){return this._isInvalid?"":this._port?this._host+":"+this._port:this._host},set host(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"host")},get hostname(){return this._host},set hostname(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"hostname")},get port(){return this._port},set port(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"port")},get pathname(){return this._isInvalid?"":this._isRelative?"/"+this._path.join("/"):this._schemeData},set pathname(e){if(!this._isInvalid&&this._isRelative){this._path=[];s.call(this,e,"relative path start")}},get search(){return this._isInvalid||!this._query||"?"===this._query?"":this._query},set search(e){if(!this._isInvalid&&this._isRelative){this._query="?";"?"===e[0]&&(e=e.slice(1));s.call(this,e,"query")}},get hash(){return this._isInvalid||!this._fragment||"#"===this._fragment?"":this._fragment},set hash(e){if(!this._isInvalid){this._fragment="#";"#"===e[0]&&(e=e.slice(1));s.call(this,e,"fragment")}},get origin(){var e;if(this._isInvalid||!this._scheme)return"";switch(this._scheme){case"data":case"file":case"javascript":case"mailto":return"null"}e=this.host;return e?this._scheme+"://"+e:""}};var m=t.URL;if(m){c.createObjectURL=function(e){return m.createObjectURL.apply(m,arguments)};c.revokeObjectURL=function(e){m.revokeObjectURL(e)}}t.URL=c}}()}}).call(t,a(9))}])});
\ No newline at end of file diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz Binary files differnew file mode 100644 index 00000000000..b54cae3143a --- /dev/null +++ b/vendor/project_templates/rails.tar.gz diff --git a/yarn.lock b/yarn.lock index 98da6a984d1..c9e1b630a9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,10 +41,14 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" -ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: +ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + ajv@^4.7.0: version "4.11.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.2.tgz#f166c3c11cbc6cb9dcc102a5bcfe5b72c95287e6" @@ -52,6 +56,15 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.1.5: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486" + dependencies: + co "^4.6.0" + fast-deep-equal "^0.1.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + 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" @@ -128,6 +141,10 @@ arr-flatten@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + array-find@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" @@ -136,6 +153,10 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" @@ -233,6 +254,13 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +axios@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" + dependencies: + follow-redirects "^1.2.3" + is-buffer "^1.1.5" + babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" @@ -405,14 +433,13 @@ babel-helpers@^6.23.0: babel-runtime "^6.22.0" babel-template "^6.23.0" -babel-loader@^6.2.10: - version "6.2.10" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.2.10.tgz#adefc2b242320cd5d15e65b31cea0e8b1b02d4b0" +babel-loader@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488" dependencies: - find-cache-dir "^0.1.1" - loader-utils "^0.2.11" + find-cache-dir "^1.0.0" + loader-utils "^1.0.2" mkdirp "^0.5.1" - object-assign "^4.0.1" babel-messages@^6.23.0: version "6.23.0" @@ -825,11 +852,7 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23 lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.11.0: - version "6.15.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" - -babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1: +babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -887,6 +910,10 @@ block-stream@*: dependencies: inherits "~2.0.0" +bluebird@^2.10.2: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + bluebird@^3.0.5, bluebird@^3.1.1, bluebird@^3.3.0: version "3.4.7" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" @@ -910,6 +937,17 @@ body-parser@^1.16.1: raw-body "~2.2.0" type-is "~1.6.15" +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1003,6 +1041,10 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" +buffer-indexof@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" + buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -1049,14 +1091,29 @@ callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + 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" + camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + caniuse-api@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" @@ -1106,6 +1163,21 @@ chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" +chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + cipher-base@^1.0.0, cipher-base@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" @@ -1363,6 +1435,19 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +copy-webpack-plugin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.0.1.tgz#9728e383b94316050d0c7463958f2b85c0aa8200" + dependencies: + bluebird "^2.10.2" + fs-extra "^0.26.4" + glob "^6.0.4" + is-glob "^3.1.0" + loader-utils "^0.2.15" + lodash "^4.3.0" + minimatch "^3.0.0" + node-dir "^0.1.10" + core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" @@ -1409,6 +1494,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.2: create-hash "^1.1.0" inherits "^2.0.1" +cropper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cropper/-/cropper-2.3.0.tgz#607461d4e7aa7a7fe15a26834b14b7f0c2801562" + dependencies: + jquery ">= 1.9.1" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1515,6 +1614,12 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -1523,7 +1628,13 @@ d3@^3.5.11: version "3.5.11" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.11.tgz#d130750eed0554db70e8432102f920a12407b69c" -d@^0.1.1, d@~0.1.1: +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +d@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" dependencies: @@ -1561,11 +1672,11 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@^2.1.0, debug@^2.1.1, debug@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: - ms "0.7.2" + ms "2.0.0" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" @@ -1581,6 +1692,10 @@ decompress-response@^3.2.0: dependencies: mimic-response "^1.0.0" +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -1617,6 +1732,17 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1662,6 +1788,23 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -1828,14 +1971,14 @@ engine.io@1.8.3: engine.io-parser "1.3.2" ws "1.1.2" -enhanced-resolve@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec" +enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" object-assign "^4.0.1" - tapable "^0.2.5" + tapable "^0.2.7" enhanced-resolve@~0.9.0: version "0.9.1" @@ -1865,52 +2008,52 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: - version "0.10.12" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" +es5-ext@^0.10.14, es5-ext@^0.10.8, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: + version "0.10.24" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" dependencies: es6-iterator "2" es6-symbol "~3.1" -es6-iterator@2: - version "2.0.0" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac" +es6-iterator@2, es6-iterator@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" dependencies: - d "^0.1.1" - es5-ext "^0.10.7" - es6-symbol "3" + d "1" + es5-ext "^0.10.14" + es6-symbol "^3.1" es6-map@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897" + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-set "~0.1.3" - es6-symbol "~3.1.0" - event-emitter "~0.3.4" + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" es6-promise@^3.0.2, es6-promise@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" -es6-set@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-symbol "3" - event-emitter "~0.3.4" + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" -es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" +es6-symbol@3, es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@~3.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" dependencies: - d "~0.1.1" - es5-ext "~0.10.11" + d "1" + es5-ext "~0.10.14" es6-weak-map@^2.0.1: version "2.0.1" @@ -1961,12 +2104,12 @@ eslint-import-resolver-node@^0.2.0: object-assign "^4.0.1" resolve "^1.1.6" -eslint-import-resolver-webpack@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.1.tgz#c7f8b4d5bd3c5b489457e5728c5db1c4ffbac9aa" +eslint-import-resolver-webpack@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.3.tgz#ad61e28df378a474459d953f246fd43f92675385" dependencies: array-find "^1.0.0" - debug "^2.2.0" + debug "^2.6.8" enhanced-resolve "~0.9.0" find-root "^0.1.1" has "^1.0.1" @@ -2106,12 +2249,12 @@ eve-raphael@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30" -event-emitter@~0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5" +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" dependencies: - d "~0.1.1" - es5-ext "~0.10.7" + d "1" + es5-ext "~0.10.14" event-stream@~3.3.0: version "3.3.4" @@ -2145,6 +2288,18 @@ evp_bytestokey@^1.0.0: dependencies: create-hash "^1.1.1" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -2230,6 +2385,10 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +fast-deep-equal@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2317,13 +2476,13 @@ finalhandler@1.0.3, finalhandler@~1.0.3: statuses "~1.3.1" unpipe "~1.0.0" -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" dependencies: commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" + make-dir "^1.0.0" + pkg-dir "^2.0.0" find-root@^0.1.1: version "0.1.2" @@ -2336,7 +2495,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.1.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" dependencies: @@ -2355,6 +2514,12 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" +follow-redirects@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.3.tgz#01abaeca85e3609837d9fcda3167a7e42fdaca21" + dependencies: + debug "^2.4.5" + for-in@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8" @@ -2395,6 +2560,16 @@ fs-access@^1.0.0: dependencies: null-check "^1.0.0" +fs-extra@^0.26.4: + version "0.26.7" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2455,6 +2630,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -2488,6 +2667,16 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -2514,6 +2703,16 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + good-listener@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" @@ -2554,7 +2753,7 @@ got@^7.0.0: url-parse-lax "^1.0.0" url-to-options "^1.0.1" -graceful-fs@^4.1.11, graceful-fs@^4.1.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2611,6 +2810,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-symbol-support-x@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8" @@ -2770,10 +2973,23 @@ immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" +imports-loader@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.7.1.tgz#f204b5f34702a32c1db7d48d89d5e867a0441253" + dependencies: + loader-utils "^1.0.2" + source-map "^0.5.6" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -2823,6 +3039,12 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +internal-ip@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" @@ -2837,6 +3059,10 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" @@ -2862,9 +3088,9 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" +is-buffer@^1.0.2, is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" is-builtin-module@^1.0.0: version "1.0.0" @@ -3001,7 +3227,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.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3169,7 +3395,7 @@ jquery-ujs@^1.2.1: dependencies: jquery ">=1.8.0" -jquery@>=1.8.0, jquery@^2.2.1: +"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.1.tgz#3c3e16854ad3d2ac44ac65021b17426d22ad803f" @@ -3224,6 +3450,10 @@ json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-schema-traverse@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.0.tgz#0016c0b1ca1efe46d44d37541bcdfc19dcfae0db" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -3246,6 +3476,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3305,9 +3541,9 @@ karma-sourcemap-loader@^0.3.7: dependencies: graceful-fs "^4.1.2" -karma-webpack@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.2.tgz#bd38350af5645c9644090770939ebe7ce726f864" +karma-webpack@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b" dependencies: async "~0.9.0" loader-utils "^0.2.5" @@ -3353,6 +3589,12 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.0.2" +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + optionalDependencies: + graceful-fs "^4.1.9" + latest-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb" @@ -3392,11 +3634,20 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5: +loader-utils@^0.2.15, loader-utils@^0.2.5: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -3572,6 +3823,10 @@ log4js@^0.6.31: readable-stream "~1.0.2" semver "~4.3.3" +loglevel@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -3582,6 +3837,13 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -3607,6 +3869,16 @@ macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +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" @@ -3623,6 +3895,12 @@ media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + memory-fs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" @@ -3634,6 +3912,21 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -3667,30 +3960,24 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.24.0 < 2", mime-db@~1.26.0: - version "1.26.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" - -mime-db@~1.27.0: +"mime-db@>= 1.24.0 < 2", mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" dependencies: mime-db "~1.27.0" -mime-types@~2.1.11: - version "2.1.14" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" - dependencies: - mime-db "~1.26.0" - mime@1.3.4, mime@1.3.x, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + mimic-response@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" @@ -3709,7 +3996,7 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.2.0: +minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -3723,6 +4010,10 @@ moment@2.x: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" +monaco-editor@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.8.3.tgz#523bdf2d1524db2c2dfc3cae0a7b6edc48d6dea6" + mousetrap@^1.4.6: version "1.4.6" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.4.6.tgz#eaca72e22e56d5b769b7555873b688c3332e390a" @@ -3739,6 +4030,17 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" @@ -3765,9 +4067,15 @@ nested-error-stacks@^1.0.0: dependencies: inherits "~2.0.1" -node-ensure@^0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" +node-dir@^0.1.10: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + dependencies: + minimatch "^3.0.2" + +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" node-libs-browser@^1.0.0: version "1.1.1" @@ -3875,7 +4183,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2: +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.3.5" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" dependencies: @@ -3901,6 +4209,12 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + npmlog@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" @@ -4032,6 +4346,14 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -4061,6 +4383,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + p-timeout@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" @@ -4151,6 +4477,10 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -4167,6 +4497,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" @@ -4179,17 +4515,14 @@ pbkdf2@^3.0.3: dependencies: create-hmac "^1.1.2" -pdfjs-dist@^1.8.252: - version "1.8.252" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-1.8.252.tgz#2477245695341f7fe096824dacf327bc324c0f52" - dependencies: - node-ensure "^0.0.0" - worker-loader "^0.8.0" - -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pikaday@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3" @@ -4212,6 +4545,12 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + pkg-up@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26" @@ -4603,6 +4942,10 @@ querystringify@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + randomatic@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" @@ -4680,6 +5023,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.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" @@ -4688,6 +5038,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" @@ -4761,6 +5119,13 @@ recursive-readdir@2.1.1: dependencies: minimatch "3.0.3" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -4973,6 +5338,12 @@ select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" +selfsigned@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a" + dependencies: + node-forge "0.6.33" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -5052,6 +5423,16 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + shelljs@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" @@ -5141,16 +5522,16 @@ sockjs-client@1.0.1: json3 "^3.3.2" url-parse "^1.0.1" -sockjs-client@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" dependencies: - debug "^2.2.0" + debug "^2.6.6" eventsource "0.1.6" faye-websocket "~0.11.0" inherits "^2.0.1" json3 "^3.3.2" - url-parse "^1.1.1" + url-parse "^1.1.8" sockjs@0.3.18: version "0.3.18" @@ -5169,9 +5550,9 @@ source-list-map@^0.1.7, source-list-map@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" -source-list-map@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4" +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" source-map-support@^0.4.2: version "0.4.11" @@ -5264,10 +5645,6 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -stats-webpack-plugin@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.4.3.tgz#b2f618202f28dd04ab47d7ecf54ab846137b7aea" - "statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -5348,6 +5725,16 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -5370,6 +5757,12 @@ supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-co dependencies: has-flag "^1.0.0" +supports-color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" + dependencies: + has-flag "^2.0.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -5397,9 +5790,9 @@ tapable@^0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" -tapable@^0.2.5, tapable@~0.2.5: - version "0.2.6" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tapable@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" tar-pack@~3.3.0: version "3.3.0" @@ -5452,6 +5845,10 @@ through@2, through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + timeago.js@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-2.0.5.tgz#730c74fbdb0b0917a553675a4460e3a7f80db86c" @@ -5514,6 +5911,10 @@ traverse@0.6.6: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -5551,9 +5952,9 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uglify-js@^2.6, uglify-js@^2.8.27: - version "2.8.27" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c" +uglify-js@^2.6, uglify-js@^2.8.29: + 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" @@ -5564,6 +5965,14 @@ 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" +uglifyjs-webpack-plugin@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -5638,13 +6047,20 @@ url-parse@1.0.x: querystringify "0.0.x" requires-port "1.0.x" -url-parse@^1.0.1, url-parse@^1.1.1: +url-parse@^1.0.1: version "1.1.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a" dependencies: querystringify "0.0.x" requires-port "1.0.x" +url-parse@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" + dependencies: + querystringify "~1.0.0" + requires-port "1.0.x" + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -5775,12 +6191,12 @@ vue@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.6.tgz#451714b394dd6d4eae7b773c40c2034a59621aed" -watchpack@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: async "^2.1.2" - chokidar "^1.4.3" + chokidar "^1.7.0" graceful-fs "^4.1.2" wbuf@^1.1.0, wbuf@^1.4.0: @@ -5805,35 +6221,40 @@ webpack-bundle-analyzer@^2.8.2: opener "^1.4.3" ws "^2.3.1" -webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.0.tgz#7d5be2651e692fddfafd8aaed177c16ff51f0eb8" +webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" dependencies: memory-fs "~0.4.1" mime "^1.3.4" path-is-absolute "^1.0.0" range-parser "^1.0.3" -webpack-dev-server@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.2.tgz#cf595d6b40878452b6d2ad7229056b686f8a16be" +webpack-dev-server@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.6.1.tgz#0b292a9da96daf80a65988f69f87b4166e5defe7" dependencies: ansi-html "0.0.7" + bonjour "^3.5.0" chokidar "^1.6.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" + del "^3.0.0" express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" + internal-ip "^1.2.0" + loglevel "^1.4.1" opn "4.0.2" portfinder "^1.0.9" + selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" - sockjs-client "1.1.2" + sockjs-client "1.1.4" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" - webpack-dev-middleware "^1.9.0" + webpack-dev-middleware "^1.11.0" yargs "^6.0.0" webpack-sources@^0.1.0: @@ -5843,38 +6264,43 @@ webpack-sources@^0.1.0: source-list-map "~0.1.7" source-map "~0.5.3" -webpack-sources@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" +webpack-sources@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" dependencies: - source-list-map "^1.1.1" + source-list-map "^2.0.0" source-map "~0.5.3" -webpack@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07" +webpack-stats-plugin@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" + +webpack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" - ajv "^4.7.0" - ajv-keywords "^1.1.1" + ajv "^5.1.5" + ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.0.0" + enhanced-resolve "^3.4.0" + escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" json5 "^0.5.1" loader-runner "^2.3.0" - loader-utils "^0.2.16" + loader-utils "^1.1.0" memory-fs "~0.4.1" mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" - uglify-js "^2.8.27" - watchpack "^1.3.1" - webpack-sources "^0.2.3" - yargs "^6.0.0" + supports-color "^4.2.1" + tapable "^0.2.7" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.4.0" + webpack-sources "^1.0.1" + yargs "^8.0.2" websocket-driver@>=0.3.6, websocket-driver@>=0.5.1: version "0.6.5" @@ -5894,7 +6320,11 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@^1.1.1, which@^1.2.1: +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.1.1, which@^1.2.1, which@^1.2.9: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: @@ -5922,12 +6352,6 @@ wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" -worker-loader@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-0.8.0.tgz#13582960dcd7d700dc829d3fd252a7561696167e" - dependencies: - loader-utils "^1.0.2" - wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -5999,6 +6423,12 @@ yargs-parser@^4.2.0: dependencies: camelcase "^3.0.0" +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + yargs@^6.0.0: version "6.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" @@ -6017,6 +6447,24 @@ yargs@^6.0.0: y18n "^3.2.1" yargs-parser "^4.2.0" +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" |