Comparar commits

...

80 Commits

Autor SHA1 Mensagem Data
Wallace Breza 3603a39800 Fixes local file system unit tests 2020-11-04 14:31:30 -08:00
Wallace Breza 69bb890d34 Fixes canvas unit test 2020-11-04 14:28:57 -08:00
Wallace Breza 80dcb2704a Fixes export form tests 2020-11-04 14:19:50 -08:00
Wallace Breza 81c4603a53 Fixes asset service unit tests 2020-11-04 14:09:21 -08:00
Wallace Breza 1d135c89ce Fixes tfrecord asset tests 2020-11-04 14:07:59 -08:00
Wallace Breza 728cb69500 Fixes azure custom vision service tests 2020-11-04 14:02:43 -08:00
Wallace Breza 1fda295971 Merge branch 'pjlittle/update-dependencies' of https://github.com/microsoft/VoTT into pjlittle/update-dependencies 2020-11-04 13:50:55 -08:00
Wallace Breza c1f563f6da Fixes unit tests on export providers 2020-11-04 13:50:50 -08:00
P.J. Little 176094bfde Merge branch 'pjlittle/update-dependencies' of github.com:microsoft/VoTT into pjlittle/update-dependencies 2020-11-04 13:47:33 -08:00
P.J. Little 833e8a0b4d Fixes for updated @types/jest typings. 2020-11-04 13:43:38 -08:00
Wallace Breza 7d856705a9 Fixes unit tests in connection service 2020-11-04 13:40:50 -08:00
Wallace Breza 8306fe58a8 Refactors failing unit tests 2020-11-04 13:24:47 -08:00
Wallace Breza b0b3d56e14 Fixes export provider tests 2020-11-04 11:54:56 -08:00
Wallace Breza 8af3da0445 Fixes project service tests 2020-11-04 11:51:56 -08:00
P.J. Little 8a1d0e95f8 Debugging jest/mock failures 2020-11-03 16:17:29 -08:00
P.J. Little 1b91ce81be More dependencies updates, warning clean-up. 2020-11-03 14:01:49 -08:00
Wallace Breza c9b2ff0d7a Installs webpack cli and updates electron/builder 2020-11-03 13:11:09 -08:00
Wallace Breza f9ace95cd8 Updates to reference node 12.x 2020-11-03 12:51:35 -08:00
P.J. Little 8dfea49b81 WIP - updated some dependencies 2020-11-03 11:57:13 -08:00
Wallace Breza 931115d59c Update node-sass (#1018)
The current version of node-sass is very outdated which is causing build errors due to 404 not found requests to components no longer available.

This PR updates to latest version of node sass and confirms local development can be setup.
2020-11-02 10:32:35 -08:00
Vott 2876444f21 release: Update minor version to 2.2.0 ***NO_CI*** 2020-06-02 23:04:22 +00:00
My da28e80bc0 fix: sidebar not showing thumbnails (#969)
Co-authored-by: Vott <vott@microsoft.com>
2020-06-02 15:07:35 -07:00
My 80add1ae25 Merge pull request #964 from microsoft/myho/updateReleasePipeline
chore: update & document release process
2020-06-01 11:46:20 -07:00
Vott dba6dfebfa remove commneted out code 2020-06-01 11:02:06 -07:00
Vott a2689e290c update docs 2020-05-18 19:45:38 -07:00
Vott 7498260038 chore: update & document release process 2020-05-18 15:45:49 -07:00
My 94daa110e9 Merge pull request #957 from microsoft/myho/mergeDevelop
release: merge develop to master
2020-05-07 11:26:14 -07:00
My Ho 9154f5178e fix: tests failing on windows agent 2020-04-29 14:33:17 -07:00
My Ho 94f09908a1 Merge branch 'develop' 2020-04-29 09:32:07 -07:00
Ayaka Hara 347f609096 feat: Simplified Chinese language support (#948)
Co-authored-by: siliang-jiao <44962887+siliang-jiao@users.noreply.github.com>
Co-authored-by: Siliang Jiao <Siliang.Jiao@microsoft.com>
2020-04-28 09:16:05 -07:00
Ayaka Hara 2b68e213a1 feat: Korean language support (#950)
Co-authored-by: Eunji Kim <eunk.angie@outlook.com>
2020-04-27 09:38:47 -07:00
Ayaka Hara 0d5fdd00ca feat: Traditional Chinese language support (#949)
Co-authored-by: Rex Tang (TOMOHIKO YUKAWA) <retang@microsoft.com>
Co-authored-by: Rex Tang <rex.tang@microsoft.com>
Co-authored-by: Rex Tang <rextangtw@gmail.com>
Co-authored-by: Jason Chou <harvinchou@hotmail.com>
Co-authored-by: John Chang <john.chang@microsoft.com>
2020-04-21 12:13:48 -07:00
My a19d2b7688 Upgrade windows & mac agents per AzDO removing older support (#945)
* Upgrade windows & mac agents per AzDO removing older support

* fix service connection issues
* disable server deploy
2020-03-25 11:53:05 -07:00
Bart Jansen ed2d28fa99 feat: Japanese language support (#922)
* fix: Update new link for VoTT site (#907)

* feat: Japanese language support

* fixed tslint errors

* fixed  variables for japanese

* added JP PR feedback suggestions

* added JP PR feedback suggestions

Co-authored-by: Tanner Barlow <tabarlow@microsoft.com>
2020-03-06 10:32:25 -06:00
My 51462af4d6 chore: install dependabot (#872)
* include target branch
* update lables
2019-11-21 16:28:42 -08:00
Tanner Barlow c1afb7e355 fix: Update new link for VoTT site (#907) 2019-10-04 16:05:16 -07:00
John Shewchuk 54473577f8 feat: add web server for login support 2019-09-03 13:45:23 -07:00
Wallace Breza 90577ca8a0 Revert horizontal asset bar (#881)
The horizontal asset bar looks more visually appealing on the bottom and it also opens up more real estate for the main editing surface.

However, it also reduces the overall usability of the app. The vertical scrolling behavior on the moues wheel no longer works and the keyboard shortcut hot keys are inverted.

Let's revert this change for now until we are able to overcome these critical usability requirements.
2019-08-26 16:52:47 -07:00
Luisa de Miranda 11c962b8e8 feat: add preview of filepaths to project settings source and target connection dropdowns (#877) 2019-08-23 16:20:55 -07:00
My a4f9f652ac chore: pipeline for preview release (queue manually) (#879) 2019-08-23 15:58:49 -07:00
Elizabeth Halper 15881b7999 feat: move asset preview to horizontal (#876)
feat: change sidebar from vertical to horizontal

Moved the vertical asset previewer to be horizontal and at the bottom of the screen. This helps maximize the size of the picture being tagged when it is a landscape photo.

AB#860
2019-08-23 13:30:37 -07:00
hermanho 9d64f4aa0d fix: test asset distribution to include all tags on test/train split (#823)
* fix: test asset distribution to include all tags on test/train split

The test asset may not included all tags when export with test/train split option in current venison (2.1.0).

* Extract the same split logic into helper function

* Formatting

* Inverting if statement
2019-08-23 10:39:02 -07:00
Tanner Barlow c0201ca51a Revert "feat: move asset preview to horizontal (#870)" (#874)
This reverts commit 5e25dc5406.
2019-08-22 14:23:52 -07:00
Elizabeth Halper 5e25dc5406 feat: move asset preview to horizontal (#870)
feat: change sidebar from vertical to horizontal

Moved the vertical asset previewer to be horizontal and at the bottom of the screen. This helps maximize the size of the picture being tagged when it is a landscape photo.

AB#860
2019-08-22 10:59:24 -07:00
Tanner Barlow add4680e7b fix: Resolve UnhandledPromiseRejection in test (#859) 2019-08-22 10:43:38 -07:00
Tanner Barlow 60ebb41540 Merge pull request #856 from microsoft/master
release: Master into dev
2019-08-20 12:02:42 -07:00
Guilherme Danno e4ee4cd008 fix: remove duplicated border-radius CSS (#843) 2019-08-20 06:30:36 -07:00
mloenow 6df00da380 fix: Use image/jpeg as MIME type for canvas.toBlob (#844)
Retargeted. See https://github.com/microsoft/VoTT/pull/801.

Closes https://github.com/microsoft/VoTT/pull/801, https://github.com/microsoft/VoTT/issues/838.
2019-08-19 14:16:05 -07:00
Wallace Breza 44f9e4b42f fix: Updates backwards compat & fixes cntk export image bug (#789)
Fixes an issue where the images exported out of a video file were missing file extension for video projects.
2019-04-29 14:11:02 -07:00
Wallace Breza c38daca7eb fix: Updates export options for pascalVOC rename (#788)
Adds a check during project load to update the export options if project was using previous pascalVOC name.
2019-04-29 11:48:27 -07:00
Lee, Jebum dba3130447 fix: change method for alloc string to buffer (#777)
String.length is not appropriate for calculating buffer size
when non-alphabet letter is included in content.
Change the method Buffer.alloc to Buffer.from as directed by the nodejs document.
2019-04-29 11:29:16 -04:00
Jacopo Mangiavacchi bae83386dd feat: Add CSV Exporter (#757)
Adds CSV export provider
2019-04-26 18:36:53 -04:00
Tanner Barlow ee6cc77ccf fix: Fix display of tag color picker (#782)
Resolves issue of tag color picker not being shown on alt-click or color-click + edit button. Also adds several tests for increased test coverage of tagInput.tsx
2019-04-26 08:20:28 -07:00
Wallace Breza 443155786a feat: Active Learning Updates (#778)
Adds new active learning form
Moves active learning settings from project settings to here
Refactored and created activeLearningService
2019-04-23 17:46:24 -07:00
P.J. Little 655d7b71d0 docs: updates to readme and changelog (#781)
Minor updates and corrections to the main readme and changelog.
2019-04-23 14:21:44 -07:00
Wallace Breza 6ff265708c Create CODE_OF_CONDUCT.md (#779)
Adds code of conduct
2019-04-23 08:42:01 -07:00
Wallace Breza 276f2842c4 doc: Add bug & feature templates (#780)
Adds bug and feature github templates
2019-04-23 08:41:36 -07:00
Tanner Barlow 77745098f3 refactor: Remove editor footer (#774)
Cleanup of leftover editor footer that is no longer used
2019-04-19 14:16:14 -07:00
Tanner Barlow 54ecbe197d fix: Rename/delete tags throughout assets (#764)
Renames or deletes tags from asset/project files when tags are renamed/deleted in tag input component

Solves [AB#17862]
2019-04-19 13:11:55 -07:00
Wallace Breza 4d99c1cdfb test: Verify tag update/delete project actions 2019-04-19 11:58:56 -07:00
Wallace Breza d1cd728454 test: Refactored editor page tests 2019-04-19 11:58:56 -07:00
Wallace Breza 466902fb2f fix: Refactored project tag/delete updates 2019-04-19 11:58:55 -07:00
Tanner Barlow f45cf244b2 Dummy commit to kick off build again 2019-04-19 11:58:35 -07:00
Tanner Barlow 390ab50f5a Lint fixes 2019-04-19 11:58:35 -07:00
Tanner Barlow 48ac5db75c Clean up and docs 2019-04-19 11:58:34 -07:00
Tanner Barlow 1eb23e0a0f Fix tag removal test for editor page 2019-04-19 11:58:34 -07:00
Tanner Barlow d08b90bc87 Rename tag function in editor page 2019-04-19 11:58:33 -07:00
Tanner Barlow 33223630e9 Delete tag working 2019-04-19 11:58:33 -07:00
Tanner Barlow 57f430f023 Saving assets in async foreach loop 2019-04-19 11:58:33 -07:00
Tanner Barlow f11edfafb5 Register mixins and run async loop 2019-04-19 11:58:32 -07:00
Tanner Barlow 0fa8dba216 Editor Page and canvas upates 2019-04-19 11:58:32 -07:00
Tanner Barlow 2f6ff66b33 Split out project functions and asset functions into respective services 2019-04-19 11:58:31 -07:00
Tanner Barlow 295dcadff2 Added confirm and strings for tag and rename operations 2019-04-19 11:58:31 -07:00
Tanner Barlow 94df15d825 Added tests for project service 2019-04-19 11:58:30 -07:00
Tanner Barlow cc98d5f8d1 Project service functions 2019-04-19 11:58:30 -07:00
Tanner Barlow d19a64e40b refactor: Remove editor footer 2019-04-18 13:18:25 -07:00
Wallace Breza 1e0a46082b Fix: Enables selection of azure region for custom vision export (#765)
Adds ability to select azure region where your custom vision service is hosted.
Filters domain list by project type

Resolves #759, #770
2019-04-18 11:41:00 -07:00
Wallace Breza 929c91d4c4 feat: CNTK Export Provider (#771)
Adds CNTK export provider into v2

Resolves #754
2019-04-17 16:43:31 -07:00
Wallace Breza 5574cd9d59 feat: Save partial project progress during project creation (#769)
This adds functionality to persist partial project information when creating a new project. Right now when creating a new connection inline within the create project flow and returning to the create project screen your partial project information is lost. Partial form progress is now saved into local storage and bound when returning to the form.

Resolves #758
2019-04-17 16:27:43 -07:00
Jacopo Mangiavacchi b13eaf6fd1 Fix ymax and rename Tensorflow nama everywhere (#763)
fix issue #760 [AB#18223]
2019-04-15 15:03:03 -07:00
114 arquivos alterados com 24952 adições e 12416 exclusões
+13
Ver Arquivo
@@ -0,0 +1,13 @@
version: 1
update_configs:
target_branch: "develop"
# Keep package.json (& lockfiles) up to date weekly
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"
default_labels:
- "dependencies"
- "dependabot"
+72
Ver Arquivo
@@ -0,0 +1,72 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint", "react"],
"extends": [
"plugin:@typescript-eslint/recommended",
"react-app",
"react-app/jest"
],
"rules": {
"quotes": ["off", "double"],
"@typescript-eslint/indent": [
"off",
2
],
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-parameter-properties": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-object-literal-type-assertion": 0,
"@typescript-eslint/await-thenable": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-implied-eval": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/prefer-regexp-exec": "off",
"@typescript-eslint/require-await": "off",
"@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/unbound-method": "off",
"@typescript-eslint/no-inferrable-types": "off",
"no-extend-native":"off",
"no-multi-str": "off",
"no-template-curly-in-string": "off",
"no-async-promise-executor": "off",
"no-case-declarations": "off",
"no-useless-escape": "off",
"prefer-const": "off",
"prefer-spread": "off",
"react/no-find-dom-node": "off",
"react/prop-types": "off",
"react/jsx-no-target-blank": "off",
"react/jsx-no-comment-textnodes": "off",
"import/first": "off",
"getter-return":"off",
"dot-location": "off",
"no-new-func": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/alt-text": "off",
"jest/no-identical-title": "off",
"jest/valid-expect": "off",
"jest/no-jasmine-globals": "off",
"jest/no-conditional-expect": "off",
"jest/valid-describe": "off"
}
}
+6
Ver Arquivo
@@ -19,6 +19,7 @@
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
@@ -37,3 +38,8 @@ secrets.sh
# complexity reports
es6-src/
report/
# VoTT Server
server/lib
server/node_modules
server/coverage
+4 -3
Ver Arquivo
@@ -28,6 +28,7 @@ VoTT helps facilitate an end-to-end machine learning pipeline:
<!-- toc -->
- [VoTT (Visual Object Tagging Tool)](#vott-visual-object-tagging-tool)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
@@ -58,7 +59,7 @@ VoTT helps facilitate an end-to-end machine learning pipeline:
## Getting Started
VoTT can be installed as a native application or run from source. VoTT is also available as a [stand-alone Web application](https://vott.z5.web.core.windows.net) and can be used in any modern Web browser.
VoTT can be installed as a native application or run from source. VoTT is also available as a [stand-alone Web application](https://vott.z22.web.core.windows.net) and can be used in any modern Web browser.
### Download and install a release package for your platform (recommended)
@@ -66,7 +67,7 @@ VoTT is available for Windows, Linux and OSX. Download the appropriate platform
### Build and run from source
VoTT requires [NodeJS (>= 10.x, Dubnium) and NPM](https://github.com/nodejs/Release)
VoTT requires [NodeJS (>= 12.x, Dubnium) and NPM](https://github.com/nodejs/Release)
```bash
git clone https://github.com/Microsoft/VoTT.git
@@ -80,7 +81,7 @@ VoTT requires [NodeJS (>= 10.x, Dubnium) and NPM](https://github.com/nodejs/Rele
### Run as Web Application
Using a modern Web browser, VoTT can be loaded from: [https://vott.z5.web.core.windows.net](https://vott.z5.web.core.windows.net)
Using a modern Web browser, VoTT can be loaded from: [https://vott.z22.web.core.windows.net](https://vott.z22.web.core.windows.net)
As noted above, the Web version of VoTT *cannot* access the local file system; all assets must be imported/exported through a Cloud project.
+66
Ver Arquivo
@@ -0,0 +1,66 @@
# Overview
[![Build Status](https://dev.azure.com/msft-vott/VoTT/_apis/build/status/VoTT/Create%20Release?branchName=master)](https://dev.azure.com/msft-vott/VoTT/_build/latest?definitionId=55&branchName=master)
Instruction on how to create new GitHub & Web Releases.
## Release Process
![alt text](./docs/images/release-process.png "Create Release Process")
### AzDO Tasks
[GitHub Release Task](https://github.com/microsoft/azure-pipelines-tasks/tree/master/Tasks/GitHubReleaseV1)
[Azure File Copy](https://github.com/microsoft/azure-pipelines-tasks/tree/master/Tasks/AzureFileCopyV3)
## Versioning
Follow [NPM Semantic Versioning](https://docs.npmjs.com/about-semantic-versioning#incrementing-semantic-versions-in-published-packages)
| Code status | Stage | Rule | Example version |
| ----------------------------------------- | ------------- | ------------------------------------------------------------------ | --------------- |
| First release | New product | Start with 1.0.0 | 1.0.0 |
| Backward compatible bug fixes | Patch release | Increment the third digit | 1.0.1 |
| Backward compatible new features | Minor release | Increment the middle digit and reset last digit to zero | 1.1.0 |
| Changes that break backward compatibility | Major release | Increment the first digit and reset middle and last digits to zero | 2.0.0 |
### Commands
The pipeline use [npm-version](https://docs.npmjs.com/cli/version) to update version
#### Pre
All version with `pre`, ie. `preminor` will bump the appropriate didgit & append `-0` to the new version
Examples:
`npm version prepatch`
1. v2.3.0 --> v2.3.1-0
1. v2.3.1-0 --> v2.3.2-0
`npm version preminor`
1. v2.3.0 --> v2.4.0-0
1. v2.4.0-0 --> v2.5.0-0
##### Exception
`prerelease` behave similar to prepatch, but would increment the last digit.
Examples:
`npm version prerelease`
v2.3.0 --> v2.3.1-0
v2.3.0-0 --> v2.3.0-1
#### Major
v2.x.x --> v3.0.0
#### Minor
v2.2.0 --> v2.3.0
+2 -2
Ver Arquivo
@@ -17,7 +17,7 @@ jobs:
- job: MacOS
pool:
vmImage: macOS-10.13
vmImage: macOS-10.15
timeoutInMinutes: 60 # how long to run the job before automatically cancelling
steps:
- checkout: self # self represents the repo where the initial Pipelines YAML file was found
@@ -26,7 +26,7 @@ jobs:
- job: Windows
pool:
vmImage: win1803
vmImage: "windows-2019"
timeoutInMinutes: 60 # how long to run the job before automatically cancelling
steps:
- checkout: self # self represents the repo where the initial Pipelines YAML file was found
+33
Ver Arquivo
@@ -0,0 +1,33 @@
name: "GitHub & Web Release [$(SourceBranchName)] - $(Date:yyyyMMdd)$(Rev:.r)"
trigger: none # manual queue only when we're ready to release
pr: none # disable CI build for PR
variables:
azureSubscription: "pj-little-sub"
DEV_STORAGE_ACCOUNT: 'vottdev'
PROD_STORAGE_ACCOUNT: 'vott'
DEV_URL: "https://vottdev.z5.web.core.windows.net/"
PROD_URL: "https://vott.z22.web.core.windows.net/"
stages:
- stage: version_bump_commit
jobs:
- template: templates/npm-version-bump.yml
parameters:
versionType: $(NPM_VERSION_TYPE)
- stage: github_release
dependsOn: version_bump_commit
jobs:
- template: templates/create-github-release.yml
parameters:
GitHubConnection: 'GitHub connection' # defaults for any parameters that aren't specified
repositoryName: '$(Build.Repository.Name)' # microsoft/VoTT
isPreRelease: $(IS_PRERELEASE) # set when queuing build
isDraft: $(IS_DRAFT)
- stage: web_release
dependsOn: version_bump_commit
jobs:
- template: templates/create-web-release.yml
@@ -1,26 +0,0 @@
# Node.js with React
# Build a Node.js project that uses React.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
steps:
- task: NodeTool@0
displayName: 'Use Node 10.x'
inputs:
versionSpec: 10.x
- bash: |
set -ex
# clean install
npm ci
npm run release-ci
mkdir -p linux
cp releases/vott*.snap linux/
displayName: Build
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: linux'
inputs:
PathtoPublish: linux
ArtifactName: linux
@@ -16,7 +16,7 @@ steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '12.x'
displayName: 'Install Node.js'
- bash: |
-22
Ver Arquivo
@@ -1,22 +0,0 @@
steps:
- task: NodeTool@0
displayName: 'Use Node 10.x'
inputs:
versionSpec: 10.x
- bash: |
set -ex
# clean install
npm ci
npm run release-ci
mkdir -p mac
cp releases/vott*.dmg mac/
displayName: Build
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: mac'
inputs:
PathtoPublish: mac
ArtifactName: mac
+1 -1
Ver Arquivo
@@ -5,7 +5,7 @@
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '12.x'
displayName: 'Install Node.js'
- bash: |
@@ -1,69 +0,0 @@
name: $(Date:yyyyMMdd).$(Hours)$(Minutes)$(Seconds)
variables:
AZURE_STORAGE_ACCOUNT: vottv2
trigger:
- dev*
- master
pr: none # disable CI build for PR
pool:
vmImage: 'Ubuntu-16.04'
steps:
- bash: |
set -e
COMMIT_SHA=`echo ${BUILD_SOURCEVERSION} | cut -c1-8`
echo "sha: " $COMMIT_SHA
# rewrite build number
echo "##vso[build.updatebuildnumber]Report-${COMMIT_SHA}-${BUILD_BUILDNUMBER}"
displayName: "Rewrite build number"
- bash: |
set -e
ACCOUNT=$(AZURE_STORAGE_ACCOUNT)
if [[ -z "${ACCOUNT}" ]]; then
echo "Need to set AZURE_STORAGE_ACCOUNT"
exit 1
fi
KEY=$(AZURE_STORAGE_KEY)
if [[ -z "${KEY}" ]]; then
echo "Need to set AZURE_STORAGE_KEY"
exit 1
fi
displayName: "Verify storage account cred exists"
- task: NodeTool@0
displayName: "Use Node 10.x"
inputs:
versionSpec: 10.x
- task: Npm@1
displayName: 'Run `npm ci`'
inputs:
command: custom
verbose: false
customCommand: ci
- task: AzureCLI@1
displayName: "Pull down old report and add updates"
inputs:
azureSubscription: 'PELITTLE TEAM - CSE DWR (d36d0808-a967-4f73-9fdc-32ea232fc81d)'
scriptLocation: inlineScript
inlineScript: './scripts/update-report.sh'
- bash: |
set -e
cat /tmp/download.log
displayName: "print download log"
condition: succeededOrFailed()
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: report'
inputs:
PathtoPublish: ./report
ArtifactName: report
-5
Ver Arquivo
@@ -1,5 +0,0 @@
# Overview
Collection of release definitions used in this project.
Since release does not yet support yaml, we're exporting them for
record keeping.
@@ -1,535 +0,0 @@
{
"source": 2,
"revision": 6,
"description": null,
"createdBy": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"createdOn": "2019-03-22T18:43:08.407Z",
"modifiedBy": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"modifiedOn": "2019-04-12T21:39:14.277Z",
"isDeleted": false,
"variables": {},
"variableGroups": [],
"environments": [
{
"id": 4,
"name": "Dev",
"rank": 1,
"owner": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"variables": {
"DEV_STORAGE_ACCOUNT": {
"value": "vottdev",
"allowOverride": true
}
},
"variableGroups": [],
"preDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 10
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 1
}
},
"deployStep": {
"id": 11
},
"postDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 12
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 2
}
},
"deployPhases": [
{
"deploymentInput": {
"parallelExecution": {
"parallelExecutionType": 0
},
"skipArtifactsDownload": false,
"artifactsDownloadInput": {
"downloadInputs": [
{
"alias": "_Build Web Artifact",
"artifactType": "Build",
"artifactDownloadMode": "All",
"artifactItems": []
}
]
},
"queueId": 3,
"demands": [],
"enableAccessToken": false,
"timeoutInMinutes": 0,
"jobCancelTimeoutInMinutes": 1,
"condition": "succeeded()",
"overrideInputs": {}
},
"rank": 1,
"phaseType": 1,
"name": "Deploy to dev*",
"refName": null,
"workflowTasks": [
{
"environment": {},
"taskId": "eb72cb01-a7e5-427b-a8a1-1b31ccac8a43",
"version": "2.*",
"name": "AzureBlob File Copy to $(DEV_STORAGE_ACCOUNT)",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"SourcePath": "$(System.DefaultWorkingDirectory)/_Build Web Artifact/build",
"ConnectedServiceNameSelector": "ConnectedServiceNameARM",
"ConnectedServiceName": "",
"ConnectedServiceNameARM": "28e5058e-d34a-48f3-978b-4bb47ea9e227",
"Destination": "AzureBlob",
"StorageAccount": "",
"StorageAccountRM": "$(DEV_STORAGE_ACCOUNT)",
"ContainerName": "$web",
"BlobPrefix": "",
"EnvironmentName": "",
"EnvironmentNameRM": "",
"ResourceFilteringMethod": "machineNames",
"MachineNames": "",
"vmsAdminUserName": "",
"vmsAdminPassword": "",
"TargetPath": "",
"AdditionalArgumentsForBlobCopy": "",
"AdditionalArgumentsForVMCopy": "",
"enableCopyPrerequisites": "false",
"CopyFilesInParallel": "true",
"CleanTargetBeforeCopy": "false",
"skipCACheck": "true",
"outputStorageUri": "",
"outputStorageContainerSasToken": ""
}
}
]
}
],
"environmentOptions": {
"emailNotificationType": "OnlyOnFailure",
"emailRecipients": "release.environment.owner;release.creator",
"skipArtifactsDownload": false,
"timeoutInMinutes": 0,
"enableAccessToken": false,
"publishDeploymentStatus": true,
"badgeEnabled": false,
"autoLinkWorkItems": false,
"pullRequestDeploymentEnabled": false
},
"demands": [],
"conditions": [
{
"name": "ReleaseStarted",
"conditionType": 1,
"value": ""
}
],
"executionPolicy": {
"concurrencyCount": 1,
"queueDepthCount": 0
},
"schedules": [],
"currentRelease": {
"id": 215,
"url": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/releases/215",
"_links": {}
},
"retentionPolicy": {
"daysToKeep": 30,
"releasesToKeep": 3,
"retainBuild": true
},
"processParameters": {
"dataSourceBindings": [
{
"dataSourceName": "AzureRMWebAppNamesByAppType",
"parameters": {
"WebAppKind": "$(WebAppKind)"
},
"endpointId": "$(ConnectedServiceName)",
"target": "WebAppName"
}
]
},
"properties": {},
"preDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"postDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"environmentTriggers": [],
"badgeUrl": "https://vsrm.dev.azure.com/msft-vott/_apis/public/Release/badge/4e739af6-f85e-44b9-a3f3-75eb893a6223/4/4"
},
{
"id": 7,
"name": "v2/master",
"rank": 2,
"owner": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"variables": {
"PROD_STORAGE_ACCOUNT": {
"value": "vott"
}
},
"variableGroups": [],
"preDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 19
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 1
}
},
"deployStep": {
"id": 20
},
"postDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 21
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 2
}
},
"deployPhases": [
{
"deploymentInput": {
"parallelExecution": {
"parallelExecutionType": 0
},
"skipArtifactsDownload": false,
"artifactsDownloadInput": {
"downloadInputs": [
{
"alias": "_Build Web Artifact",
"artifactType": "Build",
"artifactDownloadMode": "All",
"artifactItems": []
}
]
},
"queueId": 3,
"demands": [],
"enableAccessToken": false,
"timeoutInMinutes": 0,
"jobCancelTimeoutInMinutes": 1,
"condition": "and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/v2'), eq(variables['Build.SourceBranch'], 'refs/heads/master')))",
"overrideInputs": {}
},
"rank": 1,
"phaseType": 1,
"name": "Deploy to V2/Master",
"refName": null,
"workflowTasks": [
{
"environment": {},
"taskId": "eb72cb01-a7e5-427b-a8a1-1b31ccac8a43",
"version": "2.*",
"name": "AzureBlob File Copy to $(PROD_STORAGE_ACCOUNT)",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"SourcePath": "$(System.DefaultWorkingDirectory)/_Build Web Artifact/build",
"ConnectedServiceNameSelector": "ConnectedServiceNameARM",
"ConnectedServiceName": "",
"ConnectedServiceNameARM": "28e5058e-d34a-48f3-978b-4bb47ea9e227",
"Destination": "AzureBlob",
"StorageAccount": "",
"StorageAccountRM": "$(PROD_STORAGE_ACCOUNT)",
"ContainerName": "$web",
"BlobPrefix": "",
"EnvironmentName": "",
"EnvironmentNameRM": "",
"ResourceFilteringMethod": "machineNames",
"MachineNames": "",
"vmsAdminUserName": "",
"vmsAdminPassword": "",
"TargetPath": "",
"AdditionalArgumentsForBlobCopy": "",
"AdditionalArgumentsForVMCopy": "",
"enableCopyPrerequisites": "false",
"CopyFilesInParallel": "true",
"CleanTargetBeforeCopy": "false",
"skipCACheck": "true",
"outputStorageUri": "",
"outputStorageContainerSasToken": ""
}
}
]
}
],
"environmentOptions": {
"emailNotificationType": "OnlyOnFailure",
"emailRecipients": "release.environment.owner;release.creator",
"skipArtifactsDownload": false,
"timeoutInMinutes": 0,
"enableAccessToken": false,
"publishDeploymentStatus": true,
"badgeEnabled": false,
"autoLinkWorkItems": false,
"pullRequestDeploymentEnabled": false
},
"demands": [],
"conditions": [
{
"name": "Dev",
"conditionType": 2,
"value": "4"
},
{
"name": "_Build Web Artifact",
"conditionType": 4,
"value": "{\"sourceBranch\":\"master\",\"tags\":[],\"useBuildDefinitionBranch\":false,\"createReleaseOnBuildTagging\":false,\"tagFilter\":null}"
}
],
"executionPolicy": {
"concurrencyCount": 1,
"queueDepthCount": 0
},
"schedules": [],
"currentRelease": {
"id": 0,
"url": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/releases/0",
"_links": {}
},
"retentionPolicy": {
"daysToKeep": 30,
"releasesToKeep": 3,
"retainBuild": true
},
"processParameters": {
"dataSourceBindings": [
{
"dataSourceName": "AzureRMWebAppNamesByAppType",
"parameters": {
"WebAppKind": "$(WebAppKind)"
},
"endpointId": "$(ConnectedServiceName)",
"target": "WebAppName"
}
]
},
"properties": {},
"preDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"postDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"environmentTriggers": [],
"badgeUrl": "https://vsrm.dev.azure.com/msft-vott/_apis/public/Release/badge/4e739af6-f85e-44b9-a3f3-75eb893a6223/4/7"
}
],
"artifacts": [
{
"sourceId": "4e739af6-f85e-44b9-a3f3-75eb893a6223:36",
"type": "Build",
"alias": "_Build Web Artifact",
"definitionReference": {
"artifactSourceDefinitionUrl": {
"id": "https://dev.azure.com/msft-vott/_permalink/_build/index?collectionId=2f887b33-5e65-447a-a189-049f04641d6d&projectId=4e739af6-f85e-44b9-a3f3-75eb893a6223&definitionId=36",
"name": ""
},
"defaultVersionBranch": {
"id": "",
"name": ""
},
"defaultVersionSpecific": {
"id": "",
"name": ""
},
"defaultVersionTags": {
"id": "",
"name": ""
},
"defaultVersionType": {
"id": "latestType",
"name": "Latest"
},
"definition": {
"id": "36",
"name": "Build Web Artifact"
},
"definitions": {
"id": "",
"name": ""
},
"IsMultiDefinitionType": {
"id": "False",
"name": "False"
},
"project": {
"id": "4e739af6-f85e-44b9-a3f3-75eb893a6223",
"name": "VoTT"
},
"repository": {
"id": "",
"name": ""
}
},
"isPrimary": true,
"isRetained": false
}
],
"triggers": [
{
"artifactAlias": "_Build Web Artifact",
"triggerConditions": [
{
"sourceBranch": "dev*",
"tags": [],
"useBuildDefinitionBranch": false,
"createReleaseOnBuildTagging": false
},
{
"sourceBranch": "v2",
"tags": [],
"useBuildDefinitionBranch": false,
"createReleaseOnBuildTagging": false
},
{
"sourceBranch": "master",
"tags": [],
"useBuildDefinitionBranch": false,
"createReleaseOnBuildTagging": false
}
],
"triggerType": 1
}
],
"releaseNameFormat": "Release-$(rev:r)",
"tags": [],
"pipelineProcess": {
"type": 1
},
"properties": {
"DefinitionCreationSource": {
"$type": "System.String",
"$value": "ReleaseClone"
}
},
"id": 4,
"name": "VoTT - Deploy to Web",
"path": "\\",
"projectReference": null,
"url": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/definitions/4",
"_links": {
"self": {
"href": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/definitions/4"
},
"web": {
"href": "https://dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_release?definitionId=4"
}
}
}
@@ -1,329 +0,0 @@
{
"source": 2,
"revision": 8,
"description": "copy artifact to azure blob hosting plato report for VoTT",
"createdBy": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"createdOn": "2019-03-25T22:52:29.780Z",
"modifiedBy": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"modifiedOn": "2019-04-12T22:29:15.590Z",
"isDeleted": false,
"variables": {
"STORAGE_ACCOUNT": {
"value": "vottv2",
"allowOverride": true
}
},
"variableGroups": [],
"environments": [
{
"id": 6,
"name": "Upload Report",
"rank": 1,
"owner": {
"displayName": "My Ho 🥓🥐🍚",
"url": "https://app.vssps.visualstudio.com/Af945f528-1155-48f8-b08b-b6529166b9f0/_apis/Identities/1077cc21-332d-415a-8e93-e6277da44f6d",
"_links": {
"avatar": {
"href": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
}
},
"id": "1077cc21-332d-415a-8e93-e6277da44f6d",
"uniqueName": "myho@microsoft.com",
"imageUrl": "https://dev.azure.com/msft-vott/_apis/GraphProfile/MemberAvatars/aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1",
"descriptor": "aad.MTRlNGE0YjQtNDU5NS03OGI5LTk2MmItNTM0ZjNjYjUxMWI1"
},
"variables": {},
"variableGroups": [],
"preDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 16
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 1
}
},
"deployStep": {
"id": 17
},
"postDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 18
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 2
}
},
"deployPhases": [
{
"deploymentInput": {
"parallelExecution": {
"parallelExecutionType": 0
},
"skipArtifactsDownload": false,
"artifactsDownloadInput": {
"downloadInputs": [
{
"alias": "_VoTT - Generate latest plato report",
"artifactType": "Build",
"artifactDownloadMode": "All",
"artifactItems": []
}
]
},
"queueId": 3,
"demands": [],
"enableAccessToken": false,
"timeoutInMinutes": 0,
"jobCancelTimeoutInMinutes": 1,
"condition": "succeeded()",
"overrideInputs": {}
},
"rank": 1,
"phaseType": 1,
"name": "Upload Report",
"refName": null,
"workflowTasks": [
{
"environment": {},
"taskId": "eb72cb01-a7e5-427b-a8a1-1b31ccac8a43",
"version": "2.*",
"name": "AzureBlob File Copy To $(STORAGE_ACCOUNT)",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"SourcePath": "$(System.DefaultWorkingDirectory)/_VoTT - Generate latest plato report/report",
"ConnectedServiceNameSelector": "ConnectedServiceNameARM",
"ConnectedServiceName": "",
"ConnectedServiceNameARM": "28e5058e-d34a-48f3-978b-4bb47ea9e227",
"Destination": "AzureBlob",
"StorageAccount": "",
"StorageAccountRM": "$(STORAGE_ACCOUNT)",
"ContainerName": "$web",
"BlobPrefix": "",
"EnvironmentName": "",
"EnvironmentNameRM": "",
"ResourceFilteringMethod": "machineNames",
"MachineNames": "",
"vmsAdminUserName": "",
"vmsAdminPassword": "",
"TargetPath": "",
"AdditionalArgumentsForBlobCopy": "",
"AdditionalArgumentsForVMCopy": "",
"enableCopyPrerequisites": "false",
"CopyFilesInParallel": "true",
"CleanTargetBeforeCopy": "false",
"skipCACheck": "true",
"outputStorageUri": "",
"outputStorageContainerSasToken": ""
}
}
]
}
],
"environmentOptions": {
"emailNotificationType": "OnlyOnFailure",
"emailRecipients": "release.environment.owner;release.creator",
"skipArtifactsDownload": false,
"timeoutInMinutes": 0,
"enableAccessToken": false,
"publishDeploymentStatus": true,
"badgeEnabled": false,
"autoLinkWorkItems": false,
"pullRequestDeploymentEnabled": false
},
"demands": [],
"conditions": [
{
"name": "ReleaseStarted",
"conditionType": 1,
"value": ""
}
],
"executionPolicy": {
"concurrencyCount": 1,
"queueDepthCount": 0
},
"schedules": [],
"currentRelease": {
"id": 216,
"url": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/releases/216",
"_links": {}
},
"retentionPolicy": {
"daysToKeep": 30,
"releasesToKeep": 3,
"retainBuild": true
},
"processParameters": {
"dataSourceBindings": [
{
"dataSourceName": "AzureRMWebAppNamesByAppType",
"parameters": {
"WebAppKind": "$(WebAppKind)"
},
"endpointId": "$(ConnectedServiceName)",
"target": "WebAppName"
}
]
},
"properties": {},
"preDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"postDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"environmentTriggers": [],
"badgeUrl": "https://vsrm.dev.azure.com/msft-vott/_apis/public/Release/badge/4e739af6-f85e-44b9-a3f3-75eb893a6223/6/6"
}
],
"artifacts": [
{
"sourceId": "4e739af6-f85e-44b9-a3f3-75eb893a6223:41",
"type": "Build",
"alias": "_VoTT - Generate latest plato report",
"definitionReference": {
"artifactSourceDefinitionUrl": {
"id": "https://dev.azure.com/msft-vott/_permalink/_build/index?collectionId=2f887b33-5e65-447a-a189-049f04641d6d&projectId=4e739af6-f85e-44b9-a3f3-75eb893a6223&definitionId=41",
"name": ""
},
"defaultVersionBranch": {
"id": "",
"name": ""
},
"defaultVersionSpecific": {
"id": "",
"name": ""
},
"defaultVersionTags": {
"id": "",
"name": ""
},
"defaultVersionType": {
"id": "latestType",
"name": "Latest"
},
"definition": {
"id": "41",
"name": "VoTT - Generate latest plato report"
},
"definitions": {
"id": "",
"name": ""
},
"IsMultiDefinitionType": {
"id": "False",
"name": "False"
},
"project": {
"id": "4e739af6-f85e-44b9-a3f3-75eb893a6223",
"name": "VoTT"
},
"repository": {
"id": "",
"name": ""
}
},
"isPrimary": true,
"isRetained": false
}
],
"triggers": [
{
"artifactAlias": "_VoTT - Generate latest plato report",
"triggerConditions": [
{
"sourceBranch": "dev*",
"tags": [],
"useBuildDefinitionBranch": false,
"createReleaseOnBuildTagging": false
},
{
"sourceBranch": "master",
"tags": [],
"useBuildDefinitionBranch": false,
"createReleaseOnBuildTagging": false
}
],
"triggerType": 1
}
],
"releaseNameFormat": "Release-$(Date:MMddyy).$(Date:hhmm)-$(rev:r)",
"tags": [],
"pipelineProcess": {
"type": 1
},
"properties": {
"DefinitionCreationSource": {
"$type": "System.String",
"$value": "ReleaseClone"
}
},
"id": 6,
"name": "VoTT - Upload Plato Report",
"path": "\\",
"projectReference": null,
"url": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/definitions/6",
"_links": {
"self": {
"href": "https://vsrm.dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_apis/Release/definitions/6"
},
"web": {
"href": "https://dev.azure.com/msft-vott/4e739af6-f85e-44b9-a3f3-75eb893a6223/_release?definitionId=6"
}
}
}
+30
Ver Arquivo
@@ -0,0 +1,30 @@
# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops#job-templates-with-parameters
jobs:
- job: ${{ parameters.name }}
pool: ${{ parameters.pool }}
timeoutInMinutes: 15 # how long to run the job before automatically cancelling
steps:
- template: git-pull-current-branch.yml
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: 12.x
- bash: |
set -ex
# clean install
npm ci
npm run release-ci
OS=${{ parameters.os }}
ARTIFACT_NAME=${{ parameters.artifact }}
mkdir -p ${OS}
cp releases/${ARTIFACT_NAME} ${OS}/
displayName: Build
- publish: $(System.DefaultWorkingDirectory)/${{ parameters.os }}
artifact: ${{ parameters.os }}
@@ -0,0 +1,96 @@
parameters:
GitHubConnection: '' # defaults for any parameters that aren't specified
repositoryName: ''
isPreRelease: false
isDraft: false
jobs:
- template: build-artifact.yml
parameters:
name: LinuxArtifact
pool:
vmImage: ubuntu-16.04
os: linux
artifact: vott*.snap
- template: build-artifact.yml
parameters:
name: WindowsArtifact
pool:
vmImage: "windows-2019"
os: windows
artifact: vott*.exe
- template: build-artifact.yml
parameters:
name: MacOSArtifact
pool:
vmImage: macOS-10.15
os: mac
artifact: vott*.dmg
- job: Create_Github_Release
timeoutInMinutes: 30 # timeout on job if deploy is not completed in 30 minutes
dependsOn:
- LinuxArtifact
- WindowsArtifact
- MacOSArtifact
pool:
vmImage: ubuntu-16.04
steps:
- download: current
- template: git-pull-current-branch.yml
- bash: |
set -e
echo
echo "======> Set commit sha"
COMMIT_SHA=$(git rev-parse --short HEAD)
echo "COMMIT SHA: $COMMIT_SHA"
echo "##vso[task.setvariable variable=COMMIT_SHA]$COMMIT_SHA"
###
# These variables were set in the "Version Bump" stage. There are
# currently no way to pass variables between stages, hence this workaround.
###
echo
echo "======> Set version variables"
CURRENT_VERSION=$(cat $(Pipeline.Workspace)/variables/CURRENT_VERSION)
echo "##vso[task.setvariable variable=CURRENT_VERSION]$CURRENT_VERSION"
NEXT_VERSION=$(cat $(Pipeline.Workspace)/variables/NEXT_VERSION)
echo "##vso[task.setvariable variable=NEXT_VERSION]$NEXT_VERSION"
displayName: "Set variables for release task"
- task: GitHubRelease@1
displayName: 'GitHub release (create)'
inputs:
gitHubConnection: ${{ parameters.GitHubConnection }}
repositoryName: '$(Build.Repository.Name)'
action: 'create'
target: '$(Build.SourceBranch)'
tagSource: 'userSpecifiedTag'
tag: $(NEXT_VERSION)
releaseNotesSource: 'inline'
releaseNotesInline: |
## Web Release
### Dev
$(DEV_URL)
### Prod
$(PROD_URL)
## Docs
https://github.com/Microsoft/VoTT/blob/$(Build.SourceBranch)/README.md
assets: |
../linux/*
../windows/*
../mac/*
isDraft: ${{ parameters.isDraft }}
isPreRelease: ${{ parameters.isPrelease }}
changeLogCompareToRelease: 'lastNonDraftReleaseByTag'
changeLogCompareToReleaseTag: $(CURRENT_VERSION)
changeLogType: 'commitBased'
@@ -0,0 +1,56 @@
jobs:
- job: "Web_Release"
pool:
vmImage: 'windows-latest'
steps:
- template: git-pull-current-branch.yml
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: 12.x
- task: Npm@1
displayName: 'npm ci'
inputs:
command: custom
workingDir: .
verbose: false
customCommand: ci
- task: Bash@3
displayName: 'Create artifact'
inputs:
targetType: filePath
filePath: './scripts/generate-web-artifact.sh'
- task: AzureFileCopy@3
displayName: 'AzureBlob File Copy to $DEV_STORAGE_ACCOUNT'
inputs:
SourcePath: './build'
azureSubscription: $(azureSubscription)
destination: azureBlob
storage: $(DEV_STORAGE_ACCOUNT)
containerName: '$web'
- task: AzureFileCopy@3
displayName: 'AzureBlob File Copy to $PROD_STORAGE_ACCOUNT'
inputs:
SourcePath: './build'
azureSubscription: $(azureSubscription)
destination: azureBlob
storage: $(PROD_STORAGE_ACCOUNT)
containerName: '$web'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/master')
- bash: |
echo
echo "Dev url: $DEV_URL"
displayName: "Dev URL"
- bash: |
echo
echo "Prod url: $PROD_URL"
displayName: "Prod URL"
condition: eq(variables['Build.SourceBranch'], 'refs/heads/master')
@@ -0,0 +1,8 @@
steps:
- task: Bash@3
displayName: 'Pull latest from branch'
inputs:
targetType: filePath
filePath: './scripts/git-pull-current-branch.sh'
env:
SOURCE_BRANCH: $(Build.SourceBranch)
@@ -0,0 +1,60 @@
parameters:
versionType: ''
jobs:
- job: "version_bump"
variables:
- group: GitHub-Deploy-Creds
timeoutInMinutes: 30 # timeout on job if deploy is not completed in 30 minutes
cancelTimeoutInMinutes: 1 # time limit to wait for job to cancel
pool:
vmImage: macOS-latest # ssh key was generated on a Mac so using the same type of OS here
steps:
- task: NodeTool@0
inputs:
versionSpec: 12.x
displayName: 'Install Node.js'
# Download secure file
# Download a secure file to the agent machine
- task: DownloadSecureFile@1
# name: sshKey # The name with which to reference the secure file's path on the agent, like $(mySecureFile.secureFilePath)
inputs:
secureFile: vott_id_rsa
# Install an SSH key prior to a build or deployment
- task: InstallSSHKey@0 # https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/install-ssh-key?view=azure-devops
inputs:
knownHostsEntry: $(KNOWN_HOSTS_ENTRY)
sshPublicKey: $(SSH_PUBLIC_KEY)
#sshPassphrase: # Optional
sshKeySecureFile: vott_id_rsa
env:
KNOWN_HOSTS_ENTRY: $(KNOWN_HOSTS_ENTRY)
SSH_PUBLIC_KEY: $(SSH_PUBLIC_KEY) # map to the right format (camelCase) that Azure credentials understand
- task: Bash@3
name: BumpNpmVersion
displayName: Bump NPM Version
inputs:
targetType: filePath
filePath: ./scripts/version-bump-commit.sh
arguments:
${{ parameters.versionType }}
env:
SOURCE_BRANCH: $(Build.SourceBranch)
- bash: |
printenv | sort
mkdir -p $(System.DefaultWorkingDirectory)/variables
echo "$NEXT_VERSION" > $(System.DefaultWorkingDirectory)/variables/NEXT_VERSION
echo "$CURRENT_VERSION" > $(System.DefaultWorkingDirectory)/variables/CURRENT_VERSION
displayName: "Prep variables for publishing"
# Publish the variables folder as pipeline artifact
- publish: $(System.DefaultWorkingDirectory)/variables
artifact: variables
+74
Ver Arquivo
@@ -0,0 +1,74 @@
name: $(Date:yyyyMMdd).$(Hours)$(Minutes)$(Seconds)
variables:
AZURE_STORAGE_ACCOUNT: vottv2
azureSubscription: 'pj-little-sub'
REPORT_URL: 'https://vottv2.z22.web.core.windows.net/'
trigger:
- dev*
- master
pr: none # disable CI build for PR
pool:
vmImage: 'Ubuntu-16.04'
steps:
- bash: |
set -e
COMMIT_SHA=`echo ${BUILD_SOURCEVERSION} | cut -c1-8`
echo "sha: " $COMMIT_SHA
# rewrite build number
echo "##vso[build.updatebuildnumber]Report-${COMMIT_SHA}-${BUILD_BUILDNUMBER}"
displayName: "Rewrite build number"
- bash: |
set -e
ACCOUNT=$(AZURE_STORAGE_ACCOUNT)
if [[ -z "${ACCOUNT}" ]]; then
echo "Need to set AZURE_STORAGE_ACCOUNT"
exit 1
fi
displayName: "Verify storage account cred exists"
- task: NodeTool@0
displayName: "Use Node 12.x"
inputs:
versionSpec: 12.x
- task: Npm@1
displayName: 'Run `npm ci`'
inputs:
command: custom
verbose: false
customCommand: ci
- task: AzureCLI@1
displayName: "Pull down old report and add updates"
inputs:
azureSubscription: $(azureSubscription)
scriptLocation: inlineScript
inlineScript: './scripts/update-report.sh'
- bash: |
set -e
cat /tmp/download.log
displayName: "print download log"
condition: succeededOrFailed()
- task: AzureFileCopy@3
displayName: 'AzureBlob File Copy to $(AZURE_STORAGE_ACCOUNT)'
inputs:
SourcePath: './report'
azureSubscription: $(azureSubscription)
destination: azureBlob
storage: $(AZURE_STORAGE_ACCOUNT)
containerName: '$web'
- bash: |
echo "See report: $(REPORT_URL) "
displayName: "Report URl"
@@ -1,35 +0,0 @@
trigger:
branches:
include:
- dev*
- master
pr: none # disable CI build for PR
pool:
vmImage: 'Ubuntu-16.04'
steps:
- task: NodeTool@0
displayName: 'Use Node 10.x'
inputs:
versionSpec: 10.x
- task: Npm@1
displayName: 'npm ci'
inputs:
command: custom
workingDir: .
verbose: false
customCommand: ci
- task: Bash@3
displayName: 'Create artifact'
inputs:
targetType: filePath
filePath: './scripts/generate-web-artifact.sh'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: build'
inputs:
PathtoPublish: ./build
ArtifactName: build
@@ -1,24 +0,0 @@
steps:
- task: NodeTool@0
displayName: 'Use Node 10.x'
inputs:
versionSpec: 10.x
- bash: |
set -ex
# clean install
npm ci
npm run release-ci
mkdir -p windows
cp releases/vott*.exe windows/
displayName: Build
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: windows'
inputs:
PathtoPublish: windows
ArtifactName: windows
@@ -5,12 +5,12 @@
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '12.x'
displayName: 'Install Node.js'
- bash: |
- bash: |
set -e
export DISPLAY=:99.0
npm ci # do a clean install
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 87 KiB

+13206 -10841
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+35 -29
Ver Arquivo
@@ -1,6 +1,6 @@
{
"name": "vott",
"version": "2.1.0",
"version": "2.2.0",
"author": {
"name": "Microsoft",
"url": "https://github.com/Microsoft/VoTT"
@@ -15,14 +15,15 @@
"private": true,
"main": "build/main.js",
"dependencies": {
"@azure/storage-blob": "^10.3.0",
"@azure/storage-blob": "10.3.0",
"@tensorflow/tfjs": "^1.0.3",
"@types/snapsvg": "^0.4.35",
"axios": "^0.18.0",
"bootstrap": "^4.1.3",
"buffer-reverse": "^1.0.1",
"crypto-js": "^3.1.9-1",
"dotenv": "^7.0.0",
"express-request-id": "^1.4.1",
"fibers": "^5.0.0",
"google-protobuf": "^3.6.1",
"jpeg-js": "^0.3.4",
"json2csv": "^4.5.0",
@@ -37,13 +38,15 @@
"react": "^16.7.0",
"react-appinsights": "^3.0.0-rc.5",
"react-color": "^2.17.0",
"react-dnd": "^5.0.0",
"react-dnd-html5-backend": "^3.0.2",
"react-dom": "^16.7.0",
"react-jsonschema-form": "^1.3.0",
"react-localization": "^1.0.13",
"react-modal": "^3.8.1",
"react-redux": "^5.1.1",
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.1",
"react-router-dom": "4.3.1",
"react-scripts": "4.0.0",
"react-split-pane": "^0.1.87",
"react-tag-input": "^6.1.0",
"react-toastify": "^4.5.2",
@@ -53,6 +56,7 @@
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"rimraf": "^2.6.2",
"sass": "^1.28.0",
"shortid": "^2.2.14",
"video-react": "^0.13.2",
"vott-ct": "2.1.24",
@@ -75,8 +79,8 @@
"release-web": "npm run build && npm run webpack:prod",
"release-ci": "bash ./scripts/build.sh",
"release": "npm run build && npm run webpack:prod && electron-builder",
"pretest": "./node_modules/.bin/tslint 'src/**/*.ts*'",
"lintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix",
"pretest": "npx eslint \"src/**/*.ts*\"",
"lintfix": "npx eslint \"src/**/*.ts*\" --fix",
"test": "react-scripts test --env=jsdom --silent",
"test:ci": "cross-env CI=true npm run test",
"test:coverage": "npm run test -- --coverage",
@@ -87,51 +91,53 @@
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.5.0",
"@types/axios": "^0.14.0",
"@types/dotenv": "^6.1.0",
"@types/enzyme": "^3.1.15",
"@types/jest": "23.3.9",
"@types/jest": "26.0.15",
"@types/json2csv": "^4.4.0",
"@types/node": "10.12.7",
"@types/node": "12.19.0",
"@types/react": "16.7.6",
"@types/react-dom": "16.0.9",
"@types/react-jsonschema-form": "^1.0.12",
"@types/react-router-dom": "^4.3.1",
"@types/react-split-pane": "^0.1.67",
"@types/react-toastify": "^4.0.1",
"@types/react-router": "5.0.0",
"@types/react-router-dom": "4.3.1",
"@types/reactstrap": "^6.4.3",
"@types/redux-logger": "^3.0.6",
"@types/redux-mock-store": "^1.0.0",
"@types/snapsvg": "^0.4.35",
"cross-env": "^5.2.0",
"electron": "^3.0.13",
"electron-builder": "^20.38.3",
"electron": "^10.1.5",
"electron-builder": "^22.9.1",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.7.0",
"foreman": "^3.0.1",
"jest-enzyme": "^7.0.1",
"jquery": "^3.3.1",
"node-sass": "^4.10.0",
"node-sass": "^4.14.1",
"popper.js": "^1.14.6",
"redux-immutable-state-invariant": "^2.1.0",
"redux-logger": "^3.0.6",
"redux-mock-store": "^1.5.3",
"ts-loader": "^5.3.0",
"tslint": "^5.11.0",
"typescript": "^3.1.6",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.2",
"webpack-merge": "^4.1.5"
"typescript": "^4.0.5",
"webpack-cli": "^4.1.0"
},
"engines": {
"node": ">=10.14.2",
"npm": ">=6.4.1"
"node": ">=12.19.0",
"npm": ">=6.14.8"
}
}
-34
Ver Arquivo
@@ -1,34 +0,0 @@
trigger:
- refs/tags/* # trigger on any tag
pr: none # disable CI build for PR
jobs:
- job: Linux
condition: succeeded()
pool:
vmImage: ubuntu-16.04
timeoutInMinutes: 60 # how long to run the job before automatically cancelling
steps:
- checkout: self # self represents the repo where the initial Pipelines YAML file was found
fetchDepth: 1 # the depth of commits to ask Git to fetch
- template: azure-pipelines/linux/artifact-build-linux.yml
- job: MacOS
condition: succeeded()
pool:
vmImage: macOS-10.13
timeoutInMinutes: 60 # how long to run the job before automatically cancelling
steps:
- checkout: self # self represents the repo where the initial Pipelines YAML file was found
fetchDepth: 1 # the depth of commits to ask Git to fetch
- template: azure-pipelines/mac/artifact-build-mac.yml
- job: Windows
condition: succeeded()
pool:
vmImage: vs2017-win2016
timeoutInMinutes: 60 # how long to run the job before automatically cancelling
steps:
- checkout: self # self represents the repo where the initial Pipelines YAML file was found
fetchDepth: 1 # the depth of commits to ask Git to fetch
- template: azure-pipelines/windows/artifact-build-windows.yml
+7
Ver Arquivo
@@ -49,8 +49,15 @@ echo "commit=${COMMIT_SHA}"
# these are just needed for the reports; just install ad-hoc and don't save to package.json
npm install es6-plato eslint-plugin-react command-line-args --no-save
echo
echo "------> Finish installing dependencies"
echo
echo "------> Transpile TS to ES6"
# we can't do complexity analysis on TypeScript directly; transpile to ES6
rm -rf ${ES6_SRC}
tsc --noEmit false --outDir ${ES6_SRC}
echo
echo "------> Running complexity analasis ..."
node ${BASEDIR}/complexity-analysis.js --src ${ES6_SRC} --output ${REPORT_DIR} --version ${VERSION} --commit ${COMMIT_SHA}
+10
Ver Arquivo
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
# Get full branch name excluding refs/head from the env var SOURCE_BRANCH
SOURCE_BRANCH_NAME=${SOURCE_BRANCH/refs\/heads\/}
echo "SOURCE_BRANCH: ${SOURCE_BRANCH_NAME}"
git pull origin ${SOURCE_BRANCH_NAME}
git checkout ${SOURCE_BRANCH_NAME}
echo "Checked out branch: ${SOURCE_BRANCH_NAME}"
+40
Ver Arquivo
@@ -0,0 +1,40 @@
#!/bin/bash
set -euo pipefail
NPM_VERSION_TYPE=${1:-"prepatch --preid=preview"}
echo "Next version type: ----->$NPM_VERSION_TYPE<-----"
PACKAGE_VERSION=$(node -pe "require('./package.json').version")
CURRENT_VERSION="v$PACKAGE_VERSION"
# Get full branch name excluding refs/head from the env var SOURCE_BRANCH
SOURCE_BRANCH_NAME=${SOURCE_BRANCH/refs\/heads\/}
# Configure git to commit as VoTT Service Account
echo "Configuring git to use deploy key..."
git config --local user.email "vott@microsoft.com"
git config --local user.name "Vott"
echo "SOURCE_BRANCH: ${SOURCE_BRANCH_NAME}"
git pull origin ${SOURCE_BRANCH_NAME}
git checkout ${SOURCE_BRANCH_NAME}
echo "Checked out branch: ${SOURCE_BRANCH_NAME}"
## format: v2.2.0
NEXT_VERSION=`npm version ${NPM_VERSION_TYPE} -m "release: Update ${NPM_VERSION_TYPE} version to %s ***NO_CI***"`
echo "Set next version to: ${NEXT_VERSION}"
# There is currently no way to pass variables between stages, hence this workaround
echo
echo "##vso[task.setvariable variable=NEXT_VERSION]$NEXT_VERSION"
echo "##vso[task.setvariable variable=CURRENT_VERSION]$CURRENT_VERSION"
#### Push new tag
SHA=`git rev-parse HEAD`
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
git remote add authOrigin git@github.com:microsoft/VoTT.git
git push authOrigin ${SOURCE_BRANCH_NAME} --tags
echo
echo "Pushed new tag: ${NEXT_VERSION} @ SHA: ${SHA:0:8}"
+5
Ver Arquivo
@@ -0,0 +1,5 @@
APP_ID=xyz
APP_SECRET=asdf
COOKIE_SECRETS="[ { key: '12345678901234567890123456789012', iv: '123456789012' }, { key: 'abcdefghijklmnopqrstuvwxyzabcdef', iv: 'abcdefghijkl' }, ])"
ALLOW_HTTP=true
BASE_URL=http://localhost:3000/
+35
Ver Arquivo
@@ -0,0 +1,35 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch via NPM",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script",
"debug"
],
"port": 9229
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/lib/app.js", //"${workspaceFolder}\\lib\\app.js",
"args": [
"|",
"bunyan"
],
"outFiles": [
"${workspaceFolder}/**/*.js"
],
"console": "internalConsole",
"outputCapture": "std",
}
]
}
+24
Ver Arquivo
@@ -0,0 +1,24 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"problemMatcher": [
"$tsc"
],
"group": "build"
},
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
],
"group": "build"
}
]
}
+75
Ver Arquivo
@@ -0,0 +1,75 @@
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger: none
pr: none
variables:
# Azure Resource Manager connection created during pipeline creation
azureSubscription: fe7b93fe-e836-4a55-804c-883dbea6af24'
# Web app name
webAppName: 'vott'
# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- script: |
npm install
npm run build --if-present
# npm run test --if-present
workingDirectory: $(System.DefaultWorkingDirectory)/server
displayName: 'npm install, build and test'
- task: ArchiveFiles@2
displayName: 'Archive files'
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/server'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
replaceExistingArchive: true
- upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
artifact: drop
- stage: Deploy
displayName: Deploy stage
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: Deploy
environment: 'development'
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
displayName: 'Azure Web App Deploy: vott'
inputs:
azureSubscription: $(azureSubscription)
appType: webAppLinux
appName: $(webAppName)
runtimeStack: 'NODE|10.10'
package: $(Pipeline.Workspace)/drop/$(Build.BuildId).zip
startUpCommand: 'npm run start'
+6
Ver Arquivo
@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: "src",
coverageDirectory: "../coverage",
};
+7320
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+62
Ver Arquivo
@@ -0,0 +1,62 @@
{
"name": "vott-server",
"version": "1.0.0",
"description": "Server to support VoTT with login",
"main": "./lib/app.js",
"scripts": {
"build": "tsc",
"start": "node ./lib/app.js",
"test:unit": "jest --runInBand",
"test": "npm run lint && npm run test:unit",
"watch": "concurrently --kill-others \"tsc -w\" \"nodemon --inspect ./lib/app.js\"",
"lint": "tslint -q -p . -c tslint.json",
"lint:fix": "tslint --fix -p . -c tslint.json",
"debug": "nodemon --inspect ./lib/app.js | bunyan"
},
"author": "Microsoft",
"license": "MIT",
"dependencies": {
"@microsoft/microsoft-graph-client": "^1.7.0",
"body-parser": "^1.15.2",
"bunyan": "*",
"cookie-parser": "^1.4.3",
"cookie-session": "^1.3.3",
"cookies": "^0.7.3",
"ejs": ">= 0.0.0",
"ejs-locals": ">= 0.0.0",
"express": "^4.17.1",
"express-request-id": "^1.4.1",
"method-override": "^3.0.0",
"morgan": "^1.9.1",
"node-fetch": "^2.6.0",
"passport": "*",
"passport-azure-ad": "^4.1.0",
"simple-oauth2": "^2.2.1"
},
"devDependencies": {
"@types/bunyan": "^1.8.6",
"@types/cookie-parser": "^1.4.2",
"@types/cookie-session": "^2.0.37",
"@types/cookies": "^0.7.2",
"@types/dotenv": "^6.1.1",
"@types/express": "^4.17.1",
"@types/express-request-id": "^1.4.1",
"@types/jest": "^24.0.17",
"@types/method-override": "0.0.31",
"@types/morgan": "^1.7.37",
"@types/node-fetch": "^2.5.0",
"@types/passport": "^1.0.1",
"@types/passport-azure-ad": "^4.0.3",
"@types/simple-oauth2": "^2.2.1",
"concurrently": "^4.1.1",
"dotenv": "^8.1.0",
"jest": "^24.8.0",
"nodemon": "^1.19.1",
"ts-jest": "^24.0.2",
"tslint": "^5.18.0",
"typescript": "^3.6.2"
},
"engines": {
"node": ">= 10.0.0"
}
}
+71
Ver Arquivo
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<title>Simple Integration Tests</title>
</head>
<body>
<h2>Simple integration tests</h2>
<p>You must be logged in to use tests</p>
<button onclick="test_get_me()">GET /api/v1.0/me</button><br />
<button onclick="test_get_profile()">GET /api/v1.0/profile</button><br />
<button onclick="test_put_profile()">PUT /api/v1.0/profile { "foo": "bar" }</button><br />
<br />
<button onclick="test_get_connection()">GET /api/v1.0/cloudconnections/connection1</button><br />
<button onclick="test_put_connection()">PUT /api/v1.0/cloudconnections/connection1 { "foo": "bar" }</button><br />
<button onclick="test_put_connection_alt()">PUT /api/v1.0/cloudconnections/connection1 { "foo": "baz" }</button><br />
<button onclick="test_patch_connection()">PATCH /api/v1.0/cloudconnections/connection1 { "updated": ${now} }</button><br />
<button onclick="test_delete_connection()">DELETE /api/v1.0/cloudconnections/connection1</button><br />
<pre id='result'></pre>
<script>
function fetchOptions(method, thing) {
return { method, body: JSON.stringify(thing), headers: { 'Content-Type': 'application/json' } };
}
async function displayResponse(response) {
let json = null;
try { json = await response.json().catch(); } catch (err) { }
let result = (response.ok ? '*success*' : '*failed*') + '\n' + (json ? JSON.stringify(json, undefined, 2) : '');
document.getElementById("result").innerHTML = result;
}
async function test_get_me(e) {
let response = await fetch('/api/v1.0/me');
displayResponse(response);
}
async function test_get_profile(e) {
let response = await fetch('/api/v1.0/profile');
displayResponse(response);
}
async function test_put_profile(e) {
let response = await fetch('/api/v1.0/profile', fetchOptions("PUT", { foo: "bar" }));
displayResponse(response);
}
async function test_get_connection(e) {
let response = await fetch('/api/v1.0/cloudconnections/connection1');
displayResponse(response);
}
async function test_put_connection(e) {
let response = await fetch('/api/v1.0/cloudconnections/connection1', fetchOptions("PUT", { foo: "bar" }));
displayResponse(response);
}
async function test_put_connection_alt(e) {
let response = await fetch('/api/v1.0/cloudconnections/connection1', fetchOptions("PUT", { foo: "baz" }));
displayResponse(response);
}
async function test_patch_connection(e) {
let now = (new Date(Date.now())).toISOString();
let response = await fetch('/api/v1.0/cloudconnections/connection1', fetchOptions("PATCH", { updated: now }));
displayResponse(response);
}
async function test_delete_connection(e) {
let response = await fetch('/api/v1.0/cloudconnections/connection1', fetchOptions("DELETE", undefined));
displayResponse(response);
}
</script>
</body>
</html>
+8
Ver Arquivo
@@ -0,0 +1,8 @@
<% if (!user) { %>
<h2>Welcome! Please log in.</h2>
<a href="/login">Log In</a>
<% } else { %>
<p>Profile ID: <%= user.oid %></p>
<p>Email: <%= user.mail %></p>
<pre><%- JSON.stringify(user, null, 2) %></pre>
<% } %>
+14
Ver Arquivo
@@ -0,0 +1,14 @@
<% if (!user) { %>
<h2>Welcome! Please log in.</h2>
<a href="/login">Log In</a></br>
<a href="https://myapps.microsoft.com">Manage your permissions (organization)</a></br>
<a href="https://account.live.com/consent/Manage">Manage account permissions (personal)</a>
<% } else { %>
<h2>Hello, <%= user.displayName %></h2>
<a href="/account">Account Info</a></br>
<a href="/public/test.html">Run tests</a></br>
<a href="/endsession">End session</a></br>
<a href="/logout">Log Out</a></br>
<a href="https://myapps.microsoft.com">Manage your permissions (organization)</a></br>
<a href="https://account.live.com/consent/Manage">Manage account permissions (personal)</a>
<% } %>
+21
Ver Arquivo
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Passport-OpenID Example</title>
</head>
<body>
<% if (!user) { %>
<p>
<a href="/">Home</a> |
<a href="/login">Log In</a>
</p>
<% } else { %>
<p>
<a href="/">Home</a> |
<a href="/account">Account</a> |
<a href="/logout">Log Out</a>
</p>
<% } %>
<%- body %>
</body>
</html>
+50
Ver Arquivo
@@ -0,0 +1,50 @@
// import { default as fetch } from 'node-fetch';
import * as config from '../config';
import { app, server } from '../app';
import { ServerResponse } from 'http';
beforeAll(async (done) => {
if (!server.listening) {
server.on('listening', done())
} else {
done();
}
});
afterAll(async (done) => {
server.close(() => {
console.log('done');
done();
});
});
describe('App Server', () => {
afterAll(async (done) => {
server.close(() => {
console.log('done');
done();
});
});
test('initialized', async (done) => {
expect(app.name).toBeDefined();
expect(server.listening).toBe(true);
done();
});
test('loads app.html', async (done) => {
const response = await fetch(config.baseUrl);
expect(response.status).toBe(200);
done();
});
test('redirects to login', async (done) => {
const response = await fetch(config.baseUrl + '/api/v1.0/me');
expect(response.status).toBe(404);
done();
});
});
+13
Ver Arquivo
@@ -0,0 +1,13 @@
// import { default as fetch } from 'node-fetch';
import * as config from '../config';
describe('App Server', () => {
test('should be closed', async (done) => {
// do nothing
done();
});
});
+364
Ver Arquivo
@@ -0,0 +1,364 @@
import * as bodyParser from 'body-parser';
import * as morgan from 'morgan';
import * as bunyan from 'bunyan';
import * as cookieParser from 'cookie-parser';
import * as express from 'express';
import cookieSession = require('cookie-session');
import * as methodOverride from 'method-override';
import * as passport from 'passport';
import * as passportAzureAD from 'passport-azure-ad';
import * as config from './config';
import * as path from 'path';
import * as express_request_id from 'express-request-id';
import * as simple_oath2 from 'simple-oauth2';
import * as graph from './graph';
import * as oauth2 from './oauth2';
export const log = bunyan.createLogger({
name: 'BUNYAN-LOGGER',
src: true,
});
// -----------------------------------------------------------------------------
// Passport Setup Follows
// -----------------------------------------------------------------------------
// Setup schemas for Passport's User object and the the session persistence format.
// Augument Passport's request.user with the Azure AD oauthToken
declare global {
namespace Express {
interface User {
oid: string;
oauthToken: oauth2.Token;
}
}
}
type FullUserSchema = Express.User; // This now includes the above declaration for User.
interface MinimizedUserSchema {
oid: string;
refresh_token: string;
}
passport.serializeUser((user: FullUserSchema, done) => {
const stored: MinimizedUserSchema = { oid: user.oid, refresh_token: user.oauthToken.refresh_token };
done(null, stored);
});
passport.deserializeUser(async (stored: MinimizedUserSchema, done) => {
if (!stored || !stored.refresh_token) { return done(Error('no user profile')); }
let oauthClient = await oauth2.client(stored);
if (oauthClient.expired()) {
oauthClient = await oauthClient.refresh(); // must reassign here.
}
const profile = await graph.user(oauthClient.token.access_token).catch(reason => { log.error('could not retrieve profile', reason); });
if (!profile) { return done(Error('no user profile')); }
const result = { ...profile, oid: stored.oid, oauthToken: oauthClient.token };
return done(null, result);
});
// -----------------------------------------------------------------------------
// Define the AzureAD OIDCStrategy Strategy
// -----------------------------------------------------------------------------
const OIDCStrategyTemplate = {} as passportAzureAD.IOIDCStrategyOptionWithoutRequest;
const azureStrategyOptions: passportAzureAD.IOIDCStrategyOptionWithRequest = {
identityMetadata: config.creds.identityMetadata,
clientID: config.creds.clientID,
responseType: config.creds.responseType as typeof OIDCStrategyTemplate.responseType,
responseMode: config.creds.responseMode as typeof OIDCStrategyTemplate.responseMode,
redirectUrl: config.creds.redirectUrl,
allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl,
clientSecret: config.creds.clientSecret,
validateIssuer: config.creds.validateIssuer,
isB2C: config.creds.isB2C,
issuer: config.creds.issuer,
passReqToCallback: true,
scope: config.creds.scope,
loggingLevel: config.creds.logLevel as typeof OIDCStrategyTemplate.loggingLevel,
nonceLifetime: config.creds.nonceLifetime,
nonceMaxAmount: config.creds.nonceMaxAmount,
useCookieInsteadOfSession: config.creds.useCookieInsteadOfSession,
cookieEncryptionKeys: config.creds.cookieEncryptionKeys,
clockSkew: config.creds.clockSkew,
};
// -----------------------------------------------------------------------------
// Use the Azure OIDCStrategy within Passport.
//
// Strategies in passport require a `verify` function, which accepts credentials
// (in this case, the `oid` claim in id_token), and invoke a callback to find
// the corresponding user object.
//
// The following are the accepted prototypes for the `verify` function
// (1) function(iss, sub, done)
// (2) function(iss, sub, profile, done)
// (3) function(iss, sub, profile, access_token, refresh_token, done)
// (4) function(iss, sub, profile, access_token, refresh_token, params, done)
// (5) function(iss, sub, profile, jwtClaims, access_token, refresh_token, params, done)
// (6) prototype (1)-(5) with an additional `req` parameter as the first parameter
//
// To do prototype (6), passReqToCallback must be set to true in the config.
// -----------------------------------------------------------------------------
passport.use(new passportAzureAD.OIDCStrategy(azureStrategyOptions, processAzureStrategy));
async function processAzureStrategy(req: express.Request,
iss: string, sub: string, profile: passportAzureAD.IProfile, jwtClaims: any,
access_token: string, refresh_token: string, oauthToken: any,
done: passportAzureAD.VerifyCallback) {
if (!profile.oid) {
return done(new Error('No oid found'), null);
}
// asynchronous verification, for effect...
process.nextTick(async () => {
const fullProfile = await graph.user(access_token);
if (!fullProfile) {
return done(Error('no profile'));
}
const oauth = oauth2.client(oauthToken);
const result = { ...fullProfile, oid: profile.oid, oauthToken: oauth.token };
return done(null, result);
});
}
// -----------------------------------------------------------------------------
// Config the express app and all the required middleware
// -----------------------------------------------------------------------------
export const app = express();
app.use(morgan(config.httpLogFormat));
app.set('trust proxy', true);
app.set('views', path.join(__dirname, '../public/views'));
app.set('view engine', 'ejs');
app.use(express_request_id());
app.use(methodOverride());
app.use(cookieParser());
app.use(cookieSession({ keys: config.creds.cookieEncryptionKeys.map(value => value.key), secure: false, maxAge: 1000 * 60 * 60 * 24 * 365 }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(passport.session());
// -----------------------------------------------------------------------------
// Define a couple of authencation request handlers.
// -----------------------------------------------------------------------------
function ensureAuthenticated(req: express.Request, res: express.Response, next: express.NextFunction) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login');
}
function ensureAuthenticatedApi(req: express.Request, res: express.Response, next: express.NextFunction) {
if (req.isAuthenticated()) { return next(); }
res.sendStatus(401).end();
return next();
}
app.get('/', (req, res) => {
const user = { ...req.user, oauthToken: '[removed]'};
res.render('index', { user });
});
app.get('/account', ensureAuthenticated, (req, res, next) => {
const user = { ...req.user, oauthToken: '[removed]'};
res.render('account', { user });
});
app.get('/login', (req, res, next) => {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
customState: 'my_state', // optional. Provide a value if you want to provide custom state value.
failureRedirect: '/',
} as passport.AuthenticateOptions,
)(req, res, next);
},
(req, res) => {
log.info('login was called');
res.redirect('/');
});
// 'GET returnURL'
// `passport.authenticate` will try to authenticate the content returned in
// query (such as authorization code). If authentication fails, user will be
// redirected to '/' (home page); otherwise, it passes to the next middleware.
app.get('/auth/openid/return', (req, res, next) => {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/',
} as passport.AuthenticateOptions,
)(req, res, next);
},
(req, res, next) => {
log.info('received a return from AzureAD.');
res.redirect('/');
});
// 'POST returnURL'
// `passport.authenticate` will try to authenticate the content returned in
// body (such as authorization code). If authentication fails, user will be
// redirected to '/' (home page); otherwise, it passes to the next middleware.
app.post('/auth/openid/return',
(req, res, next) => {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/',
} as passport.AuthenticateOptions,
)(req, res, next);
},
(req, res, next) => {
log.info('received a return from AzureAD.');
res.redirect('/');
});
// 'endsession' route, logout from passport, and destroy the session with AAD.
app.get('/endsession', (req, res) => {
req.session = null;
req.logOut();
res.redirect('/');
});
// 'logout' route, logout from passport, and destroy the session with AAD.
app.get('/logout', (req, res) => {
req.session = null;
// req.session.destroy((err) => {
req.logOut();
res.redirect(config.destroySessionUrl);
});
app.get('/api/v1.0/me', ensureAuthenticatedApi,
async (req, res, next) => {
try {
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const result = await graph.user(oauth.token.access_token);
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.get('/api/v1.0/profile', ensureAuthenticatedApi,
async (req, res, next) => {
try {
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const result = await graph.client(oauth.token.access_token).api('/me/extensions/com.code-with.vott').get();
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.put('/api/v1.0/profile', ensureAuthenticatedApi,
async (req, res, next) => {
try {
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const body = { ...req.body, extensionName: 'com.code-with.vott' };
let result = null;
try { // Handle bad graph open extension semantics
result = await graph.client(oauth.token.access_token).api('/me/extensions/').post(body);
} catch (error) {
// if it already exists and we are replacing. Delete and try again.
result = await graph.client(oauth.token.access_token).api('/me/extensions/com.code-with.vott').delete();
result = await graph.client(oauth.token.access_token).api('/me/extensions').post(body);
}
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.get('/api/v1.0/cloudconnections/:id', ensureAuthenticatedApi,
async (req, res, next) => {
try {
const id = req.params.id; // careful should only be a domain name pattern.
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const result = await graph.client(oauth.token.access_token).api(`/me/extensions/com.code-with.vott.${id}`).get();
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.put('/api/v1.0/cloudconnections/:id', ensureAuthenticatedApi,
async (req, res, next) => {
try {
const id = req.params.id; // careful should only be a domain name pattern.
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const body = { ...req.body, extensionName: `com.code-with.vott.${id}` };
let result = null;
try { // Handle bad graph open extension semantics
result = await graph.client(oauth.token.access_token).api('/me/extensions/').post(body);
} catch (error) {
// if it already exists and we are replacing. Delete and try again.
result = await graph.client(oauth.token.access_token).api(`/me/extensions/com.code-with.vott.${id}`).delete();
result = await graph.client(oauth.token.access_token).api('/me/extensions').post(body);
}
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.patch('/api/v1.0/cloudconnections/:id', ensureAuthenticatedApi,
async (req, res, next) => {
try {
const id = req.params.id; // careful should only be a domain name pattern.
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const body = { ...req.body, extensionName: `com.code-with.vott.${id}` };
const result = await graph.client(oauth.token.access_token).api(`/me/extensions/com.code-with.vott.${id}`).patch(body);
res.json(result);
res.end();
next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.delete('/api/v1.0/cloudconnections/:id', ensureAuthenticatedApi,
async (req, res, next) => {
try {
const id = req.params.id; // careful should only be a domain name pattern.
let oauth = oauth2.client(req.user.oauthToken);
if (oauth.expired()) { oauth = await oauth.refresh(); }
const result = await graph.client(oauth.token.access_token).api(`/me/extensions/com.code-with.vott.${id}`).delete();
res.end();
return next();
} catch (error) {
res.status(error.statusCode).json(error).end();
return next();
}
});
app.use('/public', express.static(path.join(__dirname, '../public')));
export const server = app.listen(config.port);
+94
Ver Arquivo
@@ -0,0 +1,94 @@
// tslint:disable-next-line: no-var-requires
require('dotenv').config();
export const baseUrl = process.env.BASE_URL || 'http://localhost:3000/';
export const redirectPath = 'auth/openid/return';
export const port = process.env.PORT || '3000';
export const loggingLevel = process.env.LOGGING_LEVEL || 'info';
export const httpLogFormat = process.env.HTTP_LOG_FORMAT || 'dev';
console.log('config values', process.env.APP_ID, process.env.APP_SECRET, baseUrl, redirectPath, port, loggingLevel, httpLogFormat);
export const creds = {
// Required
identityMetadata: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',
// 'https://login.microsoftonline.com/<tenant_name>.onmicrosoft.com/v2.0/.well-known/openid-configuration',
// or equivalently: 'https://login.microsoftonline.com/<tenant_guid>/v2.0/.well-known/openid-configuration'
//
// or you can use the common endpoint
// 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration'
// To use the common endpoint, you have to either turn `validateIssuer` off, or provide the `issuer` value.
// Required, the client ID of your app in AAD
clientID: process.env.APP_ID,
// Required, must be 'code', 'code id_token', 'id_token code' or 'id_token'
// If you want to get access_token, you must use 'code', 'code id_token' or 'id_token code'
responseType: 'code id_token',
// Required
responseMode: 'form_post',
// Required, the reply URL registered in AAD for your app
redirectUrl: baseUrl + redirectPath,
// Required if we use http for redirectUrl
allowHttpForRedirectUrl: process.env.ALLOW_HTTP ? process.env.ALLOW_HTTP === 'true' : true,
// Required if `responseType` is 'code', 'id_token code' or 'code id_token'.
// If app key contains '\', replace it with '\\'.
clientSecret: process.env.APP_SECRET,
// Required to set to false if you don't want to validate issuer
validateIssuer: false,
// Required if you want to provide the issuer(s) you want to validate instead of using the issuer from metadata
// issuer could be a string or an array of strings of the following form: 'https://sts.windows.net/<tenant_guid>/v2.0'
issuer: null as string,
// !Bug - must be false in this sample
// Required to set to true if the `verify` function has 'req' as the first parameter
passReqToCallback: true,
// Recommended to set to true. By default we save state in express session, if this option is set to true, then
// we encrypt state and save it in cookie instead. This option together with { session: false } allows your app
// to be completely express session free.
useCookieInsteadOfSession: true,
logLevel: loggingLevel,
// Required if `useCookieInsteadOfSession` is set to true. You can provide multiple set of key/iv pairs for key
// rollover purpose. We always use the first set of key/iv pair to encrypt cookie, but we will try every set of
// key/iv pair to decrypt cookie. Key can be any string of length 32, and iv can be any string of length 12.
cookieEncryptionKeys: (process.env.COOKIES_SECRETS ? JSON.parse(process.env.COOKIES_SECRETS) :
[
{ key: '12345678901234567890123456789012', iv: '123456789012' },
{ key: 'abcdefghijklmnopqrstuvwxyzabcdef', iv: 'abcdefghijkl' },
]) as Array<{ key: string; iv: string; }>,
// The additional scopes we want besides 'openid'.
// 'profile' scope is required, the rest scopes are optional.
// (1) if you want to receive refresh_token, use 'offline_access' scope
// (2) if you want to get access_token for graph api, use the graph api url like 'https://graph.microsoft.com/mail.read'
scope: ['profile', /* 'offline_access', */ 'https://graph.microsoft.com/user.readwrite'],
// Optional. The lifetime of nonce in session or cookie, the default value is 3600 (seconds).
nonceLifetime: null as number,
// Optional. The max amount of nonce saved in session or cookie, the default value is 10.
nonceMaxAmount: 5,
// Optional. The clock skew allowed in token validation, the default value is 300 seconds.
clockSkew: null as number,
// Optional. Is B2C
isB2C: false,
};
// The url you need to go to destroy the session with AAD
export let destroySessionUrl = 'https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=http://localhost:3000';
if (!creds.clientID || !creds.clientSecret) {
console.log('issue with config');
throw Error('Missing configuration. You need a .env file or environment variables for APP_ID and APP_SECRET');
}
+30
Ver Arquivo
@@ -0,0 +1,30 @@
import * as graphClient from '@microsoft/microsoft-graph-client';
export async function user(access_token: string) {
const token = client(access_token);
const result = await token.api('/me').get();
return result;
}
export async function getEvents(access_token: string) {
const token = client(access_token);
const events = await token.api('/me/events')
.select('subject,organizer,start,end')
.orderby('createdDateTime DESC')
.get();
return events;
}
export function client(access_token: string): graphClient.Client {
// Initialize Graph client
const result = graphClient.Client.init({
// Use the provided access token to authenticate
// requests
authProvider: (done: (err: any, access_token: string) => void) => {
done(null, access_token);
},
});
return result;
}
+27
Ver Arquivo
@@ -0,0 +1,27 @@
import * as express from 'express';
import * as simple_oauth2 from 'simple-oauth2';
import * as config from './config';
export const oauth2 = simple_oauth2.create({
client: {
id: config.creds.clientID,
secret: config.creds.clientSecret,
},
auth: {
tokenHost: 'https://login.microsoftonline.com/common',
authorizePath: '/oauth2/v2.0/authorize',
tokenPath: '/oauth2/v2.0/token',
},
});
export interface Token {
refresh_token: string;
access_token?: string;
expires_at?: string | Date;
}
export function client(token: Token) {
token.expires_at = token.expires_at || new Date(0);
const result = oauth2.accessToken.create(token);
return result;
}
+62
Ver Arquivo
@@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Basic Options */
"target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./lib", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
// "strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"exclude": [
"node_modules",
"coverage",
"data_models",
"public",
"lib",
"temp",
"jest.config.js",
"src/__tests__"
]
}
+48
Ver Arquivo
@@ -0,0 +1,48 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"linterOptions": {
"exclude": [
"lib",
"public",
"src/routes",
"jest.config.js"
]
},
"jsRules": {},
"rules": {
"no-console": false,
"arrow-parens": false,
"max-classes-per-file": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"align": false,
"interface-name": false,
"quotemark": [
true,
"single",
"avoid-escape",
"avoid-template"
],
"max-line-length": {
"severity": "warning",
"options": [
160,
{
"ignore-pattern": "^import |^export {(.*?)} | //"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-leading-underscore",
"allow-pascal-case",
"allow-snake-case"
]
}
}
}
+10
Ver Arquivo
@@ -12,6 +12,16 @@ import { ErrorHandler } from "./react/components/common/errorHandler/errorHandle
describe("App Component", () => {
const defaultState: IApplicationState = initialState;
const store = createReduxStore(defaultState);
const electronMock = {
ipcRenderer: {
send: jest.fn(),
on: jest.fn(),
},
};
beforeAll(() => {
delete (window as any).require;
});
function createComponent() {
return mount(
+1 -1
Ver Arquivo
@@ -136,7 +136,7 @@ export default class HtmlFileReader {
canvas.width = video.videoWidth;
const ctx = canvas.getContext("2d");
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.toBlob(resolve);
canvas.toBlob(resolve, "image/jpeg", 1.0);
};
video.onerror = reject;
if (refresh) {
+511
Ver Arquivo
@@ -0,0 +1,511 @@
import { IAppStrings } from "../strings";
/**
* App Strings for Japanese language from Google Translate
*/
export const japanese: IAppStrings = {
appName: "ビジュアル オブジェクトタグ付けツール", // Visual Object Tagging Tool,
common: {
displayName: "表示名", // Display Name,
description: "説明", // Description,
submit: "送信", // Submit,
cancel: "キャンセル", // Cancel,
save: "保存", // Save,
delete: "削除", // Delete,
provider: "プロバイダー", // Provider,
homePage: "ホーム ページ", // Home Page"
},
titleBar: {
help: "ヘルプ", // Help,
minimize: "最小化", // Minimize,
maximize: "最大化", // Maximize,
restore: "戻す", // Restore,
close: "閉じる", // Close"
},
homePage: {
newProject: "新規プロジェクト", // New Project,
openLocalProject: {
title: "ローカルプロジェクトを開く", // Open Local Project"
},
openCloudProject: {
title: "クラウドプロジェクトを開く", // Open Cloud Project,
selectConnection: "接続を選択", // Select a Connection"
},
recentProjects: "最近のプロジェクト", // Recent Projects,
deleteProject: {
title: "プロジェクトを削除", // Delete Project,
confirmation: "プロジェクトを削除してもいいですか", // Are you sure you want to delete project"
},
importProject: {
title: "プロジェクトをインポート", // Import Project,
confirmation: "プロジェクト ${project.file.name} プロジェクト設定を v2 形式に変換してもいいですか",
// Are you sure you want to conver project ${project.file.name} project settings to v2 format?
// We recommend you backup the project file first."
},
messages: {
deleteSuccess: "${project.name} を削除しました", // Successfully deleted ${project.name}"
},
},
appSettings: {
title: "アプリケーション設定", // Application Settings,
storageTitle: "ストレージ設定", // Storage Settings,
uiHelp: "設定の保存場所", // Where your settings are stored,
save: "設定の保存", // Save Settings,
securityToken: {
name: {
title: "名前", // Name"
},
key: {
title: "キー", // Key"
},
},
securityTokens: {
title: "セキュリティ トークン", // Security Tokens,
description: "セキュリティ トークンは、プロジェクト構成内の機密データを暗号化するために使用されます",
// Security tokens are used to encrypt sensitive data within your project configuration"
},
version: {
description: "バージョン:", // Version"
},
commit: "SHA をコミット", // Commit SHA,
devTools: {
description: "問題の診断に役立つアプリケーション開発者ツールを開く", // Open application developer tools to help diagnose issues,
button: "開発者ツールを開く", // Toggle Developer Tools"
},
reload: {
description: "現在の変更をすべて破棄して、アプリをリロード", // Reload the app discarding all current changes,
button: "アプリケーションをリフレッシュ", // Refresh Application"
},
messages: {
saveSuccess: "アプリケーション設定を正常に保存しました", // Successfully saved application settings"
},
},
projectSettings: {
title: "プロジェクト設定", // Project Settings,
securityToken: {
title: "セキュリティ トークン", // Security Token,
description: "プロジェクト ファイル内の機密データを暗号化するために使用されます", // Used to encrypt sensitive data within project file"
},
save: "プロジェクトを保存", // Save Project,
sourceConnection: {
title: "ソース接続", // Source Connection,
description: "アセットのロード元", // Where to load assets fro"
},
targetConnection: {
title: "ターゲット接続", // Target Connection,
description: "プロジェクトとエクスポートされたデータの保存場所", // Where to save the project and exported dat"
},
videoSettings: {
title: "ビデオ設定", // Video Settings,
description: "タグ付けにおけるフレームの抽出割合", // The rate at which frames are extracted for tagging.,
frameExtractionRate: "フレーム抽出率(ビデオ 1 秒あたりのフレーム数)", // Frame Extraction Rate (frames per a video second)"
},
addConnection: "接続を追加", // Add Connection,
messages: {
saveSuccess: "${project.name} プロジェクト設定を正常に保存しました", // Successfully saved ${project.name} project settings"
},
},
projectMetrics: {
title: "プロジェクト メトリック", // Project Metrics,
assetsSectionTitle: "アセット", // Assets,
totalAssetCount: "すべてのアセット", // Total Assets,
visitedAssets: "訪問済みアセット(${count}", // Visited Assets (${count}),
taggedAssets: "タグ付きアセット(${count}", // Tagged Assets (${count}),
nonTaggedAssets: "タグ付けされていないアセット(${count})", // Not Tagged Assets (${count}),
nonVisitedAssets: "未訪問ないアセット(${count}", // Not Visited Assets (${count}),
tagsSectionTitle: "タグ", // Tags & Labels,
totalRegionCount: "タグ付けされたすべての領域", // Total Tagged Regions,
totalTagCount: "すべてのタグ", // Total Tags,
avgTagCountPerAsset: "アセットごとの平均タグ", // Average tags per asset"
},
tags: {
title: "タグ", // Tags,
placeholder: "新しいタグを追加", // Add new tag,
editor: "タグ エディター", // Tags Editor,
modal: {
name: "タグ名", // Tag Name,
color: "タグの色", // Tag Color"
},
colors: {
white: "白", // White,
gray: "グレー", // Gray,
red: "赤", // Red,
maroon: "マルーン", // Maroon,
yellow: "黄", // Yellow,
olive: "オリーブ", // Olive,
lime: "ライム", // Lime,
green: "緑", // Green,
aqua: "アクア", // Aqua,
teal: "ティール", // Teal,
blue: "青", // Blue,
navy: "濃紺", // Navy,
fuschia: "赤紫", // Fuschia,
purple: "紫", // Purple"
},
warnings: {
existingName: "タグ名が既に存在します。別の名前を選んでください", // Tag name already exists. Choose another name,
emptyName: "空のタグ名を持つことはできません", // Cannot have an empty tag name,
unknownTagName: "不明", // Unknown"
},
toolbar: {
add: "新しいタグを追加", // Add new tag,
search: "タグを検索", // Search tags,
edit: "タグを編集", // Edit tag,
lock: "タグをロック", // Lock tag,
moveUp: "タグを上に移動", // Move tag up,
moveDown: "タグを下に移動", // Move tag down,
delete: "タグを削除", // Delete tag"
},
},
connections: {
title: "接続", // Connections,
details: "接続の詳細", // Connection Details,
settings: "接続設定", // Connection Settings,
instructions: "編集する接続を選択してください", // Please select a connection to edit,
save: "接続を保存", // Save Connection,
messages: {
saveSuccess: "${connection.name} を保存しました", // Successfully saved ${connection.name},
deleteSuccess: "${connection.name} を削除しました", // Successfully deleted ${connection.name}"
},
imageCorsWarning: "警告:Web ブラウザーで VoTT を使用する場合、CORS(クロス オリジン リソース共有)の制限により、" +
"Bing 画像検索の一部のアセットが正しくエクスポートされない場合があります。",
// Warning: When using VoTT in a Web browser, some assets from Bing Image Search may no export
// correctly due to CORS (Cross Origin Resource Sharing) restrictions.",
blobCorsWarning: "警告:ソースまたはターゲット接続として使用するには、Azure Blob Storage アカウントで CORS(クロス オリジン リソース共有)を有効にする必要があります。 ",
// Warning: CORS (Cross Domain Resource Sharing) must be enabled on the Azure Blob Storage account, in order
// to use i as a source or target connection. More information on enabling CORS can be found in the {0}",
azDocLinkText: "Azure ドキュメント", // Azure Documentation.,
providers: {
azureBlob: {
title: "Azure Blob Storage", // Azure Blob Storage,
description: "",
accountName: {
title: "アカウント名", // Account Name,
description: "",
},
containerName: {
title: "コンテナー名", // Container Name,
description: "",
},
sas: {
title: "SAS", // SAS,
description: "Blob Storage アカウントの認証に使用される共有アクセス署名",
// Shared access signature used to authenticate to the blob storage account"
},
createContainer: {
title: "コンテナーを作成", // Create Container,
description: "Blob Storage コンテナーがまだ存在しない場合は作成します",
// Creates the blob container if it does not already exist"
},
},
bing: {
title: "Bing 画像検索", // Bing Image Search,
options: "Bing 画像検索のオプション", // Bing Image Search Options,
apiKey: "APIキー", // API Key,
query: "クエリ", // Query,
aspectRatio: {
title: "アスペクト比", // Aspect Ratio,
all: "すべて", // All,
square: "正方形", // Square,
wide: "横長", // Wide,
tall: "縦長", // Tall"
},
},
local: {
title: "ローカル ファイル システム", // Local File System,
folderPath: "フォルダー パス", // Folder Path,
selectFolder: "フォルダーを選択", // Select Folder,
chooseFolder: "フォルダーを選択", // Choose Folder"
},
},
},
editorPage: {
width: "幅", // Width,
height: "高さ", // Height,
tagged: "タグ付き", // Tagged,
visited: "訪問済み", // Visited,
toolbar: {
select: "選択(V", // Select (V),
pan: "パン", // Pan,
drawRectangle: "長方形を描く", // Draw Rectangle,
drawPolygon: "ポリゴンを描く", // Draw Polygon,
copyRectangle: "長方形をコピー", // Copy Rectangle,
copy: "領域をコピー", // Copy Regions,
cut: "領域をカット", // Cut Regions,
paste: "領域を貼り付け", // Paste Regions,
removeAllRegions: "すべてのリージョンを削除", // Remove All Regions,
previousAsset: "前のアセット", // Previous Asset,
nextAsset: "次のアセット", // Next Asset,
saveProject: "プロジェクトを保存", // Save Project,
exportProject: "プロジェクトをエクスポート", // Export Project,
activeLearning: "アクティブ ラーニング", // Active Learning"
},
videoPlayer: {
previousTaggedFrame: {
tooltip: "前のタグ付きフレーム", // Previous Tagged Frame"
},
nextTaggedFrame: {
tooltip: "次のタグ付きフレーム", // Next Tagged Frame"
},
previousExpectedFrame: {
tooltip: "前のフレーム", // Previous Frame"
},
nextExpectedFrame: {
tooltip: "次のフレーム", // Next Frame"
},
},
help: {
title: "ヘルプ メニューの切り替え", // Toggle Help Menu,
escape: "ヘルプメニューを抜ける", // Escape Help Menu"
},
assetError: "アセットを読み込めません", // Unable to load asset,
tags: {
hotKey: {
apply: "ホット キーでタグを適用", // Apply Tag with Hot Key,
lock: "ホット キーでタグをロック", // Lock Tag with Hot Key"
},
rename: {
title: "タグの名前を変更", // Rename Tag,
confirmation: "このタグの名前を変更してもいいですか",
// Are you sure you want to rename this tag? It will be renamed throughout all assets"
},
delete: {
title: "タグを削除", // Delete Tag,
confirmation: "このタグを削除してもいいですか。このタグはすべてのアセットで削除され、このタグのみのあらゆる領域も削除されます",
// Are you sure you want to delete this tag? It will be deleted throughout all assets
// and any regions where this is the only tag will also be deleted"
},
},
canvas: {
removeAllRegions: {
title: "すべてのリージョンを削除", // Remove All Regions,
confirmation: "すべてのリージョンを削除してもいいですか", // Are you sure you want to remove all regions"
},
},
messages: {
enforceTaggedRegions: {
title: "無効な領域が検出されました", // Invalid region(s) detected,
description: "1 つ以上の領域にタグが付けられていません。次のアセットに進む前に、すべての領域をタグ付けしてください。",
// 1 or more regions have not been tagged. Ensure all regions ar tagged before continuing to next asset"
},
},
},
export: {
title: "エクスポート", // Export,
settings: "エクスポート設定", // Export Settings,
saveSettings: "エクスポート設定を保存", // Save Export Settings,
providers: {
common: {
properties: {
assetState: {
title: "アセットの状態", // Asset State,
description: "エクスポートに含めるアセット", // Which assets to include in the export,
options: {
all: "すべてのアセット", // All Assets,
visited: "訪問済みのアセットのみ", // Only Visited Assets,
tagged: "タグ付きアセットのみ", // Only tagged Assets"
},
},
testTrainSplit: {
title: "テスト/トレーニング分割", // Test / Train Split,
description: "エクスポートされたデータに使用するテスト/トレーニングの分割",
// The test train split to use for exported data"
},
includeImages: {
title: "画像を含める", // Include Images,
description: "ターゲット接続にバイナリ画像アセットを含めるかどうか",
// Whether or not to include binary image assets in target connection"
},
},
},
vottJson: {
displayName: "VoTT JSON", // VoTT JSO"
},
azureCV: {
displayName: "Azure Custom Vision サービス", // Azure Custom Vision Service,
regions: {
australiaEast: "オーストラリア東部", // Australia East,
centralIndia: "インド中部", // Central India,
eastUs: "米国東部", // East US,
eastUs2: "米国東部 2", // East US 2,
japanEast: "東日本", // Japan East,
northCentralUs: "米国中北部", // North Central US,
northEurope: "北ヨーロッパ", // North Europe,
southCentralUs: "アメリカ中南部", // South Central US,
southeastAsia: "東南アジア", // Southeast Asia,
ukSouth: "英国南部", // UK South,
westUs2: "米国西部 2", // West US 2,
westEurope: "西ヨーロッパ", // West Europe"
},
properties: {
apiKey: {
title: "API キー", // API Key"
},
region: {
title: "領域", // Region,
description: "サービスがデプロイされている Azure リージョン", // The Azure region where your service is deployed"
},
classificationType: {
title: "分類タイプ", // Classification Type,
options: {
multiLabel: "画像ごとに複数のタグ", // Multiple tags per image,
multiClass: "画像ごとに単一のタグ", // Single tag per image"
},
},
name: {
title: "プロジェクト名", // Project Name"
},
description: {
title: "プロジェクトの説明", // Project Description"
},
domainId: {
title: "ドメイン", // Domain"
},
newOrExisting: {
title: "新規または既存プロジェクト", // New or Existing Project,
options: {
new: "新規プロジェクト", // New Project,
existing: "既存プロジェクト", // Existing Project"
},
},
projectId: {
title: "プロジェクト名", // Project Name"
},
projectType: {
title: "プロジェクトの種類", // Project Type,
options: {
classification: "分類", // Classification,
objectDetection: "物体検出", // Object Detection"
},
},
},
},
tfRecords: {
displayName: "TensorFlow レコード", // Tensorflow Record"
},
pascalVoc: {
displayName: "Pascal VOC", // Pascal VOC,
exportUnassigned: {
title: "未割り当てをエクスポート", // Export Unassigned,
description: "エクスポートされたデータに未割り当てのタグを含めるかどうか",
// Whether or not to include unassigned tags in exported data"
},
},
cntk: {
displayName: "Microsoft Cognitive ToolkitCNTK", // Microsoft Cognitive Toolkit (CNTK)"
},
csv: {
displayName: "コンマ区切り値(CSV", // Comma Separated Values (CSV)"
},
},
messages: {
saveSuccess: "エクスポート設定を保存しました", // Successfully saved export settings"
},
},
activeLearning: {
title: "アクティブ ラーニング", // Active Learning,
form: {
properties: {
modelPathType: {
title: "モデル プロバイダー", // Model Provider,
description: "トレーニング モデルのロード元", // Where to load the training model from,
options: {
preTrained: "事前トレーニング済みの Coco SSD", // Pre-trained Coco SSD,
customFilePath: "カスタム(ファイル パス)", // Custom (File path),
customWebUrl: "カスタム(URL", // Custom (Url)"
},
},
autoDetect: {
title: "自動検出", // Auto Detect,
description: "アセット間を移動するときに自動的に予測を行うかどうか",
// Whether or not to automatically make predictions as you navigate between assets"
},
modelPath: {
title: "モデル パス", // Model path,
description: "ローカル ファイル システムからモデルを選択します", // Select a model from your local file system"
},
modelUrl: {
title: "モデルURL", // Model URL,
description: "公開 Web URL からモデルを読み込む", // Load your model from a public web URL"
},
predictTag: {
title: "予測タグ", // Predict Tag,
description: "予測にタグを自動的に含めるかどうか", // Whether or not to automatically include tags in predictions"
},
},
},
messages: {
loadingModel: "アクティブ ラーニング モデルを読み込んでいます...", // Loading active learning model...,
errorLoadModel: "アクティブ ラーニング モデルの読み込みエラー", // Error loading active learning model,
saveSuccess: "アクティブ ラーニング設定を保存しました", // Successfully saved active learning settings"
},
},
profile: {
settings: "プロファイル設定", // Profile Settings"
},
errors: {
unknown: {
title: "不明なエラー", // Unknown Error,
message: "アプリで不明なエラーが発生しました。", // The app encountered an unknown error. Please try again"
},
projectUploadError: {
title: "ファイルのアップロード エラー", // Error Uploading File,
message: "ファイルのアップロード中にエラーが発生しました。",
// There was an error uploading the file. Please verify the file is of the correct format and try again."
},
genericRenderError: {
title: "アプリケーションの読み込みエラー", // Error Loading Application,
message: "アプリケーションのレンダリング中にエラーが発生しました。",
// An error occured while rendering the application. Please try again"
},
projectInvalidSecurityToken: {
title: "プロジェクト ファイルの読み込みエラー", // Error loading project file,
message: "プロジェクトが参照するセキュリティ トークンが無効です。",
// The security token referenced by the project is invalid.
// Verify that the security token for the project has been set correctly within your application settings"
},
projectInvalidJson: {
title: "プロジェクト ファイルの解析エラー", // Error parsing project file,
message: "選択したプロジェクト ファイルに有効なJSONが含まれていません。",
// The selected project files does not contain valid JSON Please check the file any try again."
},
projectDeleteError: {
title: "プロジェクトの削除エラー", // Error deleting project,
message: "プロジェクトの削除中にエラーが発生しました。",
// An error occured while deleting the project.
// Validate the project file an security token exist and try again"
},
securityTokenNotFound: {
title: "プロジェクト ファイルの読み込みエラー", // Error loading project file,
message: "プロジェクトが参照するセキュリティ トークンが現在のアプリケーション設定に見つかりません。",
// The security token referenced by the project cannot be found in your current application settings.
// Verify the security token exists and try to reload the project."
},
canvasError: {
title: "キャンバスの読み込みエラー", // Error loading canvas,
message: "キャンバスのロード中にエラーが発生しました。プロジェクトのアセットを確認して、再試行してください。",
// There was an error loading the canvas, check the project's assets and try again."
},
importError: {
title: "V1 プロジェクトのインポート エラー", // Error importing V1 project,
message: "V1 プロジェクトのインポート中にエラーが発生しました。",
// There was an error importing the V1 project. Check the project file and try again"
},
pasteRegionTooBigError: {
title: "領域の貼り付けエラー", // Error pasting region,
message: "このアセットに対して領域が大きすぎます。別のリージョンをコピーしてください",
// Region too big for this asset. Try copying another region"
},
exportFormatNotFound: {
title: "プロジェクトのエクスポート エラー", // Error exporting project,
message: "プロジェクトにエクスポート形式がありません。",
// Project is missing export format. Please select an export format in the export setting page."
},
activeLearningPredictionError: {
title: "アクティブ ラーニングのエラー", // Active Learning Error,
message: "現在のアセットの領域を予測中にエラーが発生しました。",
// An error occurred while predicting regions in the current asset.
// Please verify your active learning configuration and try again"
},
},
};
+512
Ver Arquivo
@@ -0,0 +1,512 @@
import { IAppStrings } from "../strings";
/**
* App Strings for Korean language from Google Translate
*/
export const korean: IAppStrings = {
appName: "비주얼 객체 태깅 도구", // Visual Object Tagging Tool,
common: {
displayName: "프로젝트 이름", // Display Name,
description: "설명", // Description,
submit: "제출", // Submit,
cancel: "취소", // Cancel,
save: "저장", // Save,
delete: "삭제", // Delete,
provider: "공급자", // Provider,
homePage: "홈페이지", // Home Page"
},
titleBar: {
help: "도움", // Help,
minimize: "최소화", // Minimize,
maximize: "최대화", // Maximize,
restore: "되돌리기", // Restore,
close: "닫기", // Close"
},
homePage: {
newProject: "새로운 프로젝트", // New Project,
openLocalProject: {
title: "로컬 프로젝트 열기", // Open Local Project"
},
openCloudProject: {
title: "클라우드 프로젝트 열기", // Open Cloud Project,
selectConnection: "Connection 선택", // Select a Connection
},
recentProjects: "최근 프로젝트", // Recent Projects,
deleteProject: {
title: "프로젝트 삭제", // Delete Project,
confirmation: "프로젝트를 삭제 하시겠습니까?", // Are you sure you want to delete project
},
importProject: {
title: "프로젝트 가져 오기", // Import Project,
confirmation: "${project.file.name} 프로젝트 설정을 v2 형식으로 수정 하시겠습니까? 수정하시기 전에 프로젝트 파일을 백업해두시기 바랍니다.",
// Are you sure you want to conver project ${project.file.name} project settings to v2 format?
// We recommend you backup the project file first."
},
messages: {
deleteSuccess: "${project.name}을 삭제했습니다", // Successfully deleted ${project.name}"
},
},
appSettings: {
title: "애플리케이션 설정", // Application Settings,
storageTitle: "저장소 설정", // Storage Settings,
uiHelp: "설정이 저장된 위치", // Where your settings are stored,
save: "설정 저장", // Save Settings,
securityToken: {
name: {
title: "이름", // Name
},
key: {
title: "키", // Key
},
},
securityTokens: {
title: "보안 토큰", // Security Tokens,
description: "보안 토큰은 프로젝트 구성 내에서 중요한 데이터를 암호화하는 데 사용됩니다",
// Security tokens are used to encryp sensitive data within your project configuration"
},
version: {
description: "버전:", // Version"
},
commit: "커밋 SHA", // Commit SHA,
devTools: {
description: "이슈 진단을 돕기 위한 개발자 도구 열기", // Open application developer tools to help diagnose issues,
button: "개발자 도구 전환", // Toggle Developer Tools
},
reload: {
description: "모든 변경사항을 버리고 애플리케이션을 재시작 합니다", // Reload the app discarding all current changes,
button: "애플리케이션 새로고침", // Refresh Application
},
messages: {
saveSuccess: "애플리케이션 설정이 성공적으로 저장되었습니다", // Successfully saved application settings
},
},
projectSettings: {
title: "프로젝트 설정", // Project Settings,
securityToken: {
title: "보안 토큰", // Security Token,
description: "프로젝트 파일 내에서 중요한 데이터를 암호화하는 데 사용", // Used to encrypt sensitive data within project file
},
save: "프로젝트 저장", // Save Project,
sourceConnection: {
title: "소스 연결", // Source Connection,
description: "Asset 저장 경로", // Where to load assets from
},
targetConnection: {
title: "대상 연결", // Target Connection,
description: "프로젝트 및 내 보낸 데이터를 저장할 위치", // Where to save the project and exported data
},
videoSettings: {
title: "비디오 설정", // Video Settings,
description: "태그 지정을 위해 프레임을 추출하는 비율", // The rate at which frames are extracted for tagging
frameExtractionRate: "프레임 추출 속도 (비디오 초당 프레임)", // Frame Extraction Rate (frames per a video second)
},
addConnection: "연결 추가", // Add Connection,
messages: {
saveSuccess: "${project.name} 프로젝트 설정을 성공적으로 저장했습니다", // Successfully saved ${project.name} project settings
},
},
projectMetrics: {
title: "프로젝트 매트릭", // Project Metrics,
assetsSectionTitle: "Asset", // Assets,
totalAssetCount: "총 Asset", // Total Assets,
visitedAssets: "검토한 Asset (${count})", // Visited Assets (${count}),
taggedAssets: "태그된 Asset (${count})", // Tagged Assets (${count}),
nonTaggedAssets: "태그가 없는 Asset (${count})", // Not Tagged Assets (${count}),
nonVisitedAssets: "검토하지 않은 Asset (${count})", // Not Visited Assets (${count}),
tagsSectionTitle: "태그", // Tags & Labels,
totalRegionCount: "태그된 지역 수", // Total Tagged Regions,
totalTagCount: "태그 숫자", // Total Tags,
avgTagCountPerAsset: "Asset 당 평균 태그 숫자", // Average tags per asset"
},
tags: {
title: "태그", // Tags,
placeholder: "새 태그 추가", // Add new tag,
editor: "태그 편집기", // Tags Editor,
modal: {
name: "태그 이름", // Tag Name,
color: "태그 색상", // Tag Color"
},
colors: {
white: "하얀색", // White,
gray: "회색", // Gray,
red: "빨간색", // Red,
maroon: "밤색", // Maroon,
yellow: "노랑색", // Yellow,
olive: "올리브색", // Olive,
lime: "라임색", // Lime,
green: "초록색", // Green,
aqua: "아쿠아", // Aqua,
teal: "물오리", // Teal,
blue: "파랑색", // Blue,
navy: "군청색", // Navy,
fuschia: "푸시아", // Fuschia,
purple: "보라색", // Purple"
},
warnings: {
existingName: "태그 이름이 이미 존재합니다. 다른 이름을 입력하십시오", // Tag name already exists. Choose another name,
emptyName: "빈 태그 이름을 가질 수 없습니다", // Cannot have an empty tag name,
unknownTagName: "알 수 없는 태그 이름", // Unknown"
},
toolbar: {
add: "새 태그 추가", // Add new tag,
search: "태크 검색", // Search tags,
edit: "태그 편집", // Edit tag,
lock: "태그 잠금", // Lock tag,
moveUp: "태그를 위로 이동", // Move tag up,
moveDown: "태그를 아래로 이동", // Move tag down,
delete: "태그 삭제", // Delete tag"
},
},
connections: {
title: "연결 설정", // Connections,
details: "설명", // Connection Details,
settings: "설정", // Connection Settings,
instructions: "편집 할 연결 정보를 선택하십시오", // Please select a connection to edit,
save: "저장", // Save Connection,
messages: {
saveSuccess: "${connection.name}을 성공적으로 저장했습니다", // Successfully saved ${connection.name},
deleteSuccess: "${connection.name}을 삭제했습니다.", // Successfully deleted ${connection.name}"
},
imageCorsWarning: "경고 : 웹 브라우저에서 VoTT를 사용하는 경우 CORS (Cross Origin Resource Sharing) " +
"제한으로 인해 Bing Image Search의 일부 정보가 제대로 내보내지지 않을 수 있습니다.",
// Warning: When using VoTT in a Web browser, some assets from Bing Image Search may no export
// correctly due to CORS (Cross Origin Resource Sharing) restrictions.",
blobCorsWarning: "경고 : 소스 또는 대상 연결로 사용하려면, Azure Blob Storage 계정에서 CORS(Cross Domain Resource Sharing) " +
"설정을 활성화 해야 합니다. CORS 설정에 대한 자세한 정보는 {0}에서 찾을 수 있습니다.",
// Warning: CORS (Cross Domain Resource Sharing) must be enabled on the Azure Blob Storage account, in order
// to use i as a source or target connection. More information on enabling CORS can be found in the {0}",
azDocLinkText: "Azure 설명서.", // Azure Documentation.,
providers: {
azureBlob: {
title: "Azure Blob 저장소", // Azure Blob Storage,
description: "",
accountName: {
title: "계정 이름", // Account Name,
description: "",
},
containerName: {
title: "컨테이너 이름", // Container Name,
description: "",
},
sas: {
title: "SAS", // SAS,
description: "Blob Storage 계정을 인증하는 데 사용되는 공유 액세스 서명",
// Shared access signature used to authenticate to the blob storage account"
},
createContainer: {
title: "컨테이너 만들기", // Create Container,
description: "Blob 컨테이너가 없으면 새로 생성합니다.",
// Creates the blob container if it does not already exist"
},
},
bing: {
title: "Bing 이미지 검색", // Bing Image Search,
options: "Bing 이미지 검색 옵션", // Bing Image Search Options,
apiKey: "API 키", // API Key,
query: "쿼리", // Query,
aspectRatio: {
title: "종횡비", // Aspect Ratio,
all: "모두", // All,
square: "정사각형", // Square,
wide: "넓은", // Wide,
tall: "긴", // Tall"
},
},
local: {
title: "로컬 파일 시스템", // Local File System,
folderPath: "경로", // Folder Path,
selectFolder: "폴더 선택", // Select Folder,
chooseFolder: "선택", // Choose Folder"
},
},
},
editorPage: {
width: "너비", // Width,
height: "높이", // Height,
tagged: "태그", // Tagged,
visited: "방문", // Visited,
toolbar: {
select: "선택 (V)", // Select (V),
pan: "팬", // Pan,
drawRectangle: "사각형 그리기", // Draw Rectangle,
drawPolygon: "다각형 그리기", // Draw Polygon,
copyRectangle: "사각형 복사", // Copy Rectangle,
copy: "영역 복사", // Copy Regions,
cut: "영역 잘라내기", // Cut Regions,
paste: "영역 붙여 넣기", // Paste Regions,
removeAllRegions: "모든 지역 제거", // Remove All Regions,
previousAsset: "이전 Asset", // Previous Asset,
nextAsset: "다음 Asset", // Next Asset,
saveProject: "프로젝트 저장", // Save Project,
exportProject: "프로젝트 내보내기", // Export Project,
activeLearning: "Active Learning", // Active Learning"
},
videoPlayer: {
previousTaggedFrame: {
tooltip: "이전 태그 된 프레임", // Previous Tagged Frame"
},
nextTaggedFrame: {
tooltip: "다음 태그 된 프레임", // Next Tagged Frame"
},
previousExpectedFrame: {
tooltip: "이전 프레임", // Previous Frame"
},
nextExpectedFrame: {
tooltip: "다음 프레임", // Next Frame"
},
},
help: {
title: "도움말", // Toggle Help Menu,
escape: "나가기", // Escape Help Menu"
},
assetError: "Asset을 불러올 수 없습니다", // Unable to load asset,
tags: {
hotKey: {
apply: "단축키로 태그 적용", // Apply Tag with Hot Key,
lock: "단축키가 있는 태그 잠금", // Lock Tag with Hot Key"
},
rename: {
title: "태그 이름 바꾸기", // Rename Tag,
confirmation: "이 태그의 이름을 바꾸시겠습니까? 모든 Asset에서 이름이 변경됩니다",
// Are you sure you want to rename this tag? It will be renamed throughout all assets"
},
delete: {
title: "태그 삭제", // Delete Tag,
confirmation: "이 태그를 삭제 하시겠습니까? 모든 Asset 및 태그가 유일한 지역 인 모든 지역에서 삭제됩니다.",
// Are you sure you want to delete this tag? It will be deleted throughout all assets
// and any regions where this is the only tag will also be deleted"
},
},
canvas: {
removeAllRegions: {
title: "모든 지역 제거", // Remove All Regions,
confirmation: "모든 지역을 삭제 하시겠습니까?", // Are you sure you want to remove all regions"
},
},
messages: {
enforceTaggedRegions: {
title: "유효하지 않은 지역이 감지되었습니다.", // Invalid region(s) detected,
description: "1 개 이상의 지역이 태그되어야 합니다. 다음 작업을 계속 진행하기 위해 모든 지역에 태그가 지정되어 있는지 확인하십시오.",
// 1 or more regions have not been tagged. Ensure all regions ar tagged before continuing to next asset"
},
},
},
export: {
title: "내보내기", // Export,
settings: "내보내기 설정", // Export Settings,
saveSettings: "내보내기 설정 저장", // Save Export Settings,
providers: {
common: {
properties: {
assetState: {
title: "Asset 상태", // Asset State,
description: "내보내기에 포함 할 Asset", // Which assets to include in the export,
options: {
all: "모든 Asset", // All Assets,
visited: "방문한 Asset만", // Only Visited Assets,
tagged: "태그된 Asset만", // Only tagged Assets"
},
},
testTrainSplit: {
title: "테스트용 / 학습용 분할", // Test / Train Split,
description: "내보내는 데이터에 테스트용 / 학습용 분할",
// The test train split to use for exported data"
},
includeImages: {
title: "이미지 포함", // Include Images,
description: "대상 연결에 이진 이미지 Asset을 포함할지 여부",
// Whether or not to include binary image assets in target connection"
},
},
},
vottJson: {
displayName: "VoTT JSON", // VoTT JSON
},
azureCV: {
displayName: "Azure Custom Vision 서비스", // Azure Custom Vision Service,
regions: {
australiaEast: "호주 동부", // Australia East,
centralIndia: "중앙 인도", // Central India,
eastUs: "미국 동부", // East US,
eastUs2: "미국 동부 2", // East US 2,
japanEast: "일본 동부", // Japan East,
northCentralUs: "미국 중북부", // North Central US,
northEurope: "북유럽", // North Europe,
southCentralUs: "미국 중남부", // South Central US,
southeastAsia: "동남아시아", // Southeast Asia,
ukSouth: "영국 남부", // UK South,
westUs2: "미국 서부 2", // West US 2,
westEurope: "서유럽", // West Europe"
},
properties: {
apiKey: {
title: "API 키", // API Key"
},
region: {
title: "지역", // Region,
description: "서비스가 배포 된 Azure 지역", // The Azure region where your service is deployed"
},
classificationType: {
title: "분류 유형", // Classification Type,
options: {
multiLabel: "이미지 당 여러 태그", // Multiple tags per image,
multiClass: "이미지 당 단일 태그", // Single tag per image"
},
},
name: {
title: "프로젝트 이름", // Project Name"
},
description: {
title: "설명", // Project Description"
},
domainId: {
title: "도메인", // Domain"
},
newOrExisting: {
title: "신규 또는 기존 프로젝트", // New or Existing Project,
options: {
new: "새로운 프로젝트", // New Project,
existing: "기존 프로젝트", // Existing Project"
},
},
projectId: {
title: "프로젝트 이름", // Project Name"
},
projectType: {
title: "프로젝트 유형", // Project Type,
options: {
classification: "분류", // Classification,
objectDetection: "물체 감지", // Object Detection"
},
},
},
},
tfRecords: {
displayName: "TensorFlow 기록", // Tensorflow Record"
},
pascalVoc: {
displayName: "Pascal VOC", // Pascal VOC,
exportUnassigned: {
title: "할당되지 않은 태그 내보내기", // Export Unassigned,
description: "내보내는 데이터에 할당되지 않은 태그를 포함할지 여부",
// Whether or not to include unassigned tags in exported data"
},
},
cntk: {
displayName: "Microsoft Cognitive ToolkitCNTK", // Microsoft Cognitive Toolkit (CNTK)"
},
csv: {
displayName: "쉼표로 구분 된 값(CSV", // Comma Separated Values (CSV)"
},
},
messages: {
saveSuccess: "내보내기 설정이 성공적으로 저장되었습니다", // Successfully saved export settings"
},
},
activeLearning: {
title: "Active Learning", // Active Learning,
form: {
properties: {
modelPathType: {
title: "모델 제공자", // Model Provider,
description: "학습 모델을 불러올 위치", // Where to load the training model from,
options: {
preTrained: "미리 학습된 Coco SSD", // Pre-trained Coco SSD,
customFilePath: "사용자 정의 (파일 경로)", // Custom (File path),
customWebUrl: "사용자 정의 (URL)", // Custom (Url)"
},
},
autoDetect: {
title: "자동 감지", // Auto Detect,
description: "Asset 간을 탐색 할 때 자동 예측 여부",
// Whether or not to automatically make predictions as you navigate between assets"
},
modelPath: {
title: "모델 경로", // Model path,
description: "로컬 파일 시스템에서 모델을 선택하십시오.", // Select a model from your local file system"
},
modelUrl: {
title: "모델 URL", // Model URL,
description: "URL에서 모델 불러오기", // Load your model from a public web URL"
},
predictTag: {
title: "태그 예측", // Predict Tag,
description: "예측에 태그를 자동으로 포함할지 여부", // Whether or not to automatically include tags in predictions"
},
},
},
messages: {
loadingModel: "Active Learning 모델 불러오는 중 ...", // Loading active learning model...,
errorLoadModel: "Active Learning 모델을 불러오는 중 오류가 발생했습니다", // Error loading active learning model,
saveSuccess: "Active Learning 모델 설정을 성공적으로 저장했습니다", // Successfully saved active learning settings"
},
},
profile: {
settings: "프로필 설정", // Profile Settings"
},
errors: {
unknown: {
title: "알 수 없는 오류", // Unknown Error,
message: "애플리케이션에 알 수 없는 오류가 발생했습니다. 다시 시도하십시오.", // The app encountered an unknown error. Please try again"
},
projectUploadError: {
title: "파일 업로드 오류", // Error Uploading File,
message: "파일을 업로드하는 중에 오류가 발생했습니다. 파일이 올바른 형식인지 확인한 후 다시 시도하십시오.",
// There was an error uploading the file. Please verify the file is of the correct format and try again."
},
genericRenderError: {
title: "응용 프로그램 로딩 오류", // Error Loading Application,
message: "응용 프로그램을 렌더링하는 중에 오류가 발생했습니다. 다시 시도하십시오",
// An error occured while rendering the application. Please try again"
},
projectInvalidSecurityToken: {
title: "프로젝트 파일을 로드하는 중 오류가 발생했습니다", // Error loading project file,
message: "프로젝트에서 참조한 보안 토큰이 유효하지 않습니다.응용 프로그램 설정 내에서 프로젝트의 보안 토큰이 올바르게 설정되었는지 확인하십시오",
// The security token referenced by the project is invalid.
// Verify that the security token for the project has been set correctly within your application settings"
},
projectInvalidJson: {
title: "프로젝트 파일 파싱 오류", // Error parsing project file,
message: "선택한 프로젝트 파일에 유효한 JSON이 포함되어 있지 않습니다. 파일을 다시 확인하십시오.",
// The selected project files does not contain valid JSON Please check the file any try again."
},
projectDeleteError: {
title: "프로젝트 삭제 오류", // Error deleting project,
message: "프로젝트를 삭제하는 중에 오류가 발생했습니다. 프로젝트 파일에 보안 토큰이 존재하는지 확인한 후 다시 시도하십시오.",
// An error occured while deleting the project.
// Validate the project file an security token exist and try again"
},
securityTokenNotFound: {
title: "프로젝트 파일을 로드하는 중 오류가 발생했습니다", // Error loading project file,
message: "프로젝트가 참조하는 보안 토큰을 현재 애플리케이션 설정에서 찾을 수 없습니다. 보안 토큰이 있는지 확인하고 프로젝트를 다시로드하십시오.",
// The security token referenced by the project cannot be found in your current application settings.
// Verify the security token exists and try to reload the project."
},
canvasError: {
title: "캔버스 불러 오기 오류", // Error loading canvas,
message: "캔버스를 로드하는 중에 오류가 발생했습니다. 프로젝트 Asset을 확인한 후 다시 시도하십시오.",
// There was an error loading the canvas, check the project's assets and try again."
},
importError: {
title: "V1 프로젝트 가져 오기 오류", // Error importing V1 project,
message: "V1 프로젝트를 가져 오는 중에 오류가 발생했습니다. 프로젝트 파일을 확인하고 다시 시도하십시오.",
// There was an error importing the V1 project. Check the project file and try again"
},
pasteRegionTooBigError: {
title: "지역 붙여 넣기 오류", // Error pasting region,
message: "이 Asset에 비해 지역이 너무 큽니다. 다른 지역을 복사 해보십시오.",
// Region too big for this asset. Try copying another region"
},
exportFormatNotFound: {
title: "프로젝트 내보내기 오류", // Error exporting project,
message: "프로젝트에 내보내기 형식이 없습니다. 내보내기 설정 페이지에서 내보내기 형식을 선택하십시오.",
// Project is missing export format. Please select an export format in the export setting page."
},
activeLearningPredictionError: {
title: "Active Learning 오류", // Active Learning Error,
message: "현재 Asset의 지역을 예측하는 동안 오류가 발생했습니다. Active Learning 구성을 확인하고 다시 시도하십시오",
// An error occurred while predicting regions in the current asset.
// Please verify your active learning configuration and try again"
},
},
};
+509
Ver Arquivo
@@ -0,0 +1,509 @@
import { IAppStrings } from "../strings";
/**
* App Strings for Simplified Chinese (zh-ch)
*/
export const chinese: IAppStrings = {
appName: "视觉对象标记工具", // Visual Object Tagging Tool
common: {
displayName: "显示名称", // Display Name
description: "描述", // Description
submit: "提交", // Submit
cancel: "取消", // Cancel
save: "保存", // Save
delete: "删除", // Delete
provider: "提供者", // Provider
homePage: "主页", // Home Page
},
titleBar: {
help: "帮助", // Help
minimize: "最小化", // Minimize
maximize: "最大化", // Maximize
restore: "复原", // Restore
close: "关闭", // Close
},
homePage: {
newProject: "新项目", // New Project
openLocalProject: {
title: "打开本地项目", // Open Local Project
},
openCloudProject: {
title: "打开云端项目", // Open Cloud Project
selectConnection: "选择一个连接", // Select a Connection
},
recentProjects: "最近的项目", // Recent Projects
deleteProject: {
title: "删除项目", // Delete Project
confirmation: "确定要删除项目吗", // Are you sure you want to delete project
},
importProject: {
title: "导入项目", // Import Project
confirmation: "您确定要将项目${project.file.name}设置转换为v2格式吗?我们建议您首先备份项目文件。",
// Are you sure you want to convert project ${project.file.name} project settings to v2 format?
// We recommend you backup the project file first.
},
messages: {
deleteSuccess: "已成功删除${project.name}", // Successfully deleted ${project.name}
},
},
appSettings: {
title: "应用程序设置", // Application Settings
storageTitle: "储存设置", // Storage Settings
uiHelp: "您的设置储存在哪里", // Where your settings are stored
save: "保存设置", // Save Settings
securityToken: {
name: {
title: "名称", // Name
},
key: {
title: "密钥", // Key
},
},
securityTokens: {
title: "安全令牌", // Security Tokens
description: "安全令牌用于加密项目配置中的敏感数据",
// Security tokens are used to encrypt sensitive data within your project configuration
},
version: {
description: "版本:", // Version:
},
commit: "提交SHA", // Commit SHA
devTools: {
description: "打开应用程序开发者工具以帮助诊断问题", // Open application developer tools to help diagnose issues
button: "切换至开发者工具", // Toggle Developer Tools
},
reload: {
description: "重新加载应用,放弃当前所有更改", // Reload the app discarding all current changes
button: "刷新应用", // Refresh Application
},
messages: {
saveSuccess: "成功保存应用程序设置", // Successfully saved application settings
},
},
projectSettings: {
title: "项目设定", // Project Settings
securityToken: {
title: "安全令牌", // Security Token
description: "用于加密项目文件中的敏感数据", // Used to encrypt sensitive data within project files
},
save: "保存项目", // Save Project
sourceConnection: {
title: "源连接", // Source Connection
description: "从何处加载素材", // Where to load assets from
},
targetConnection: {
title: "目标连接", // Target Connection
description: "在哪里保存项目和导出数据", // Where to save the project and exported data
},
videoSettings: {
title: "视频设定", // Video Settings
description: "提取帧以进行标记的速率", // The rate at which frames are extracted for tagging.
frameExtractionRate: "帧提取率(每视频每秒的帧数)", // Frame Extraction Rate (frames per a video second)
},
addConnection: "添加连接", // Add Connection
messages: {
saveSuccess: "成功保存${project.name}项目设置", // Successfully saved ${project.name} project settings
},
},
projectMetrics: {
title: "项目指标", // Project Metrics
assetsSectionTitle: "素材", // Assets
totalAssetCount: "素材总数", // Total Assets
visitedAssets: "已访问素材(${count}", // Visited Assets (${count})
taggedAssets: "已标记素材(${count}", // Tagged Assets (${count})
nonTaggedAssets: "未标记素材(${count}", // Not Tagged Assets (${count})
nonVisitedAssets: "未访问素材(${count}", // Not Visited Assets (${count})
tagsSectionTitle: "标记与标签", // Tags & Labels
totalRegionCount: "标记区域总数", // Total Tagged Regions
totalTagCount: "标签总数", // Total Tags
avgTagCountPerAsset: "每个素材的平均标签数", // Average tags per asset
},
tags: {
title: "标签", // Tags
placeholder: "添加标签", // Add new tag
editor: "编辑标签", // Tags Editor
modal: {
name: "标签名称", // Tag Name
color: "标签颜色", // Tag Color
},
colors: {
white: "白色", // White
gray: "灰色", // Gray
red: "红色", // Red
maroon: "栗色", // Maroon
yellow: "黄色", // Yellow
olive: "橄榄色", // Olive
lime: "青色", // Lime
green: "绿色", // Green
aqua: "浅绿色", // Aqua
teal: "蓝绿色", // Teal
blue: "蓝色", // Blue
navy: "海军蓝色", // Navy
fuschia: "紫红色", // Fuschia
purple: "紫色", // Purple
},
warnings: {
existingName: "标签名称已存在。请选择另一个名字", // Tag name already exists. Choose another name
emptyName: "标签名称不能为空", // Cannot have an empty tag name
unknownTagName: "未知", // Unknown
},
toolbar: {
add: "添加标签", // Add new tag
search: "搜索标签", // Search tags
edit: "编辑标签", // Edit tag
lock: "锁定标签", // Lock tag
moveUp: "向上移动标签", // Move tag up
moveDown: "向下移动标签", // Move tag down
delete: "删除标签", // Delete tag
},
},
connections: {
title: "连接数", // Connections
details: "连接详细信息", // Connection Details
settings: "连接设定", // Connection Settings
instructions: "请选择一个连接进行编辑", // Please select a connection to edit
save: "保存连接", // Save Connection
messages: {
saveSuccess: "已成功保存${connection.name}", // Successfully saved ${connection.name}
deleteSuccess: "已成功删除${connection.name}", // Successfully deleted ${connection.name}
},
imageCorsWarning: "警告:在Web浏览器中使用VoTT时,由于CORS(跨源资源共享)限制,来自Bing Image Search的某些素材可能无法正确导出。",
// Warning: When using VoTT in a Web browser, some assets from Bing Image Search may not export correctly
// due to CORS (Cross Origin Resource Sharing) restrictions.
blobCorsWarning: "警告:必须在Azure Blob存储帐户上启用CORS(跨域资源共享),才能将其用作源或目标连接。 {0}中提供了有关启用CORS的更多信息。",
// Warning: CORS (Cross Domain Resource Sharing) must be enabled on the Azure Blob Storage account,
// in order to use it as a source or target connection.
// More information on enabling CORS can be found in the {0}
azDocLinkText: "Azure文档", // Azure Documentation.
providers: {
azureBlob: {
title: "Azure Blob存储", // Azure Blob Storage
description: "",
accountName: {
title: "用户名", // Account Name
description: "",
},
containerName: {
title: "容器名称", // Container Name
description: "",
},
sas: {
title: "SAS", // SAS
description: "用于验证Blob存储帐户的共享访问签名",
// Shared access signature used to authenticate to the blob storage account
},
createContainer: {
title: "创建容器", // Create Container
description: "创建blob容器(如果尚不存在)",
// Creates the blob container if it does not already exist
},
},
bing: {
title: "必应图片搜索", // Bing Image Search
options: "必应图像搜索选项", // Bing Image Search Options
apiKey: "API密钥", // API Key
query: "查询", // Query
aspectRatio: {
title: "长宽比", // Aspect Ratio
all: "所有", // All
square: "正方形", // Square
wide: "宽", // Wide
tall: "高", // Tall
},
},
local: {
title: "本地文件系统", // Local File System
folderPath: "文件夹路径", // Folder Path
selectFolder: "选择文件夹", // Select Folder
chooseFolder: "选择文件夹", // Choose Folder
},
},
},
editorPage: {
width: "宽度", // Width
height: "高度", // Height
tagged: "已标记", // Tagged
visited: "已访问", // Visited
toolbar: {
select: "选择[V]", // Select (V)
pan: "泛", // Pan
drawRectangle: "绘制矩形", // Draw Rectangle
drawPolygon: "绘制多边形", // Draw Polygon
copyRectangle: "复制矩形", // Copy Rectangle
copy: "复制区域", // Copy Regions
cut: "剪切区域", // Cut Regions
paste: "粘贴区域", // Paste Regions
removeAllRegions: "删除所有区域", // Remove All Regions
previousAsset: "以前的素材", // Previous Asset
nextAsset: "下一项素材", // Next Asset
saveProject: "保存项目", // Save Project
exportProject: "导出项目", // Export Project
activeLearning: "主动学习", // Active Learning
},
videoPlayer: {
previousTaggedFrame: {
tooltip: "上一个已标记的帧", // Previous Tagged Frame
},
nextTaggedFrame: {
tooltip: "下一个已标记的帧", // Next Tagged Frame
},
previousExpectedFrame: {
tooltip: "上一帧", // Previous Frame
},
nextExpectedFrame: {
tooltip: "下一帧", // Next Frame
},
},
help: {
title: "切换帮助菜单", // Toggle Help Menu
escape: "退出帮助菜单", // Escape Help Menu
},
assetError: "无法加载素材", // Unable to load asset
tags: {
hotKey: {
apply: "使用快捷键应用标签", // Apply Tag with Hot Key
lock: "使用快捷键锁定标签", // Lock Tag with Hot Key
},
rename: {
title: "重新命名标签", // Rename Tag
confirmation: "您确定要重新命名此标签吗?它将在所有素材中被重新命名",
// Are you sure you want to rename this tag? It will be renamed throughout all assets
},
delete: {
title: "删除标签", // Delete Tag
confirmation: "您确定要删除此标签吗?它将在所有素材中被删除,并且仅使用此标签标记的任何区域也将被删除",
// Are you sure you want to delete this tag? It will be deleted throughout all assets
// and any regions where this is the only tag will also be deleted
},
},
canvas: {
removeAllRegions: {
title: "删除所有区域", // Remove All Regions
confirmation: "您确定要删除所有区域吗?", // Are you sure you want to remove all regions?
},
},
messages: {
enforceTaggedRegions: {
title: "检测到无效的区域", // Invalid region(s) detected
description: "1个或多个区域尚未被标记。在继续下一个素材之前,请确保所有区域均已标记。",
// 1 or more regions have not been tagged.
// Ensure all regions are tagged before continuing to next asset.
},
},
},
export: {
title: "导出", // Export
settings: "导出设置", // Export Settings
saveSettings: "保存导出设置", // Save Export Settings
providers: {
common: {
properties: {
assetState: {
title: "素材状态", // Asset State
description: "导出中包括哪些素材", // Which assets to include in the export
options: {
all: "所有素材", // All Assets
visited: "仅已访问素材", // Only Visited Assets
tagged: "仅已标记素材", // Only tagged Assets
},
},
testTrainSplit: {
title: "测试/训练用数据分离", // Test / Train Split
description: "导出时分离测试/训练用数据", // The test train split to use for exported data
},
includeImages: {
title: "包含图片", // Include Images
description: "是否在目标连接中包括二值化图像素材",
// Whether or not to include binary image assets in target connection
},
},
},
vottJson: {
displayName: "VoTT JSON", // VoTT JSON
},
azureCV: {
displayName: "Azure自定义视觉服务", // Azure Custom Vision Service
regions: {
australiaEast: "澳大利亚东部", // Australia East
centralIndia: "印度中部", // Central India
eastUs: "美国东部", // East US
eastUs2: "美国东部2", // East US 2
japanEast: "日本东部", // Japan East
northCentralUs: "美国中北部", // North Central US
northEurope: "欧州北部", // North Europe
southCentralUs: "美国中南部", // South Central US
southeastAsia: "东南亚", // Southeast Asia
ukSouth: "英国南部", // UK South
westUs2: "美国西部2", // West US 2
westEurope: "欧州西部", // West Europe
},
properties: {
apiKey: {
title: "API密钥", // API Key
},
region: {
title: "区域", // Region
description: "部署服务的Azure区域", // The Azure region where your service is deployed
},
classificationType: {
title: "分类类型", // Classification Type
options: {
multiLabel: "每个图像多个标签", // Multiple tags per image
multiClass: "每个图像一个标签", // Single tag per image
},
},
name: {
title: "项目名", // Project Name
},
description: {
title: "项目简介", // Project Description
},
domainId: {
title: "域", // Domain
},
newOrExisting: {
title: "新项目或现有项目", // New or Existing Project
options: {
new: "新项目", // New Project
existing: "现有项目", // Existing Project
},
},
projectId: {
title: "项目名", // Project Name
},
projectType: {
title: "项目类型", // Project Type
options: {
classification: "分类", // Classification
objectDetection: "物体识别", // Object Detection
},
},
},
},
tfRecords: {
displayName: "Tensorflow记录", // Tensorflow Records
},
pascalVoc: {
displayName: "Pascal VOC", // Pascal VOC
exportUnassigned: {
title: "导出未分配", // Export Unassigned
description: "是否在导出的数据中包括未被分配的标签", // Whether or not to include unassigned tags in exported data
},
},
cntk: {
displayName: "Microsoft Cognitive ToolkitCNTK)", // Microsoft Cognitive Toolkit (CNTK)
},
csv: {
displayName: "逗号分隔值 (CSV)", // Comma Separated Values (CSV)
},
},
messages: {
saveSuccess: "成功保存导出设置", // Successfully saved export settings
},
},
activeLearning: {
title: "主动学习", // Active Learning
form: {
properties: {
modelPathType: {
title: "模型提供者", // Model Provider
description: "从何处加载训练模型", // Where to load the training model from
options: {
preTrained: "预先训练 Coco SSD", // Pre-trained Coco SSD
customFilePath: "自定义(文件路径)", // Custom (File path)
customWebUrl: "自定义 (URL)", // Custom (Url)
},
},
autoDetect: {
title: "自动识别", // Auto Detect
description: "在素材之间导航时是否自动进行预测",
// Whether or not to automatically make predictions as you navigate between assets
},
modelPath: {
title: "模型路径", // Model path
description: "从本地文件系统中选择模型", // Select a model from your local file system
},
modelUrl: {
title: "模型 URL", // Model URL
description: "从公共网址加载模型", // Load your model from a public web URL
},
predictTag: {
title: "预测标签", // Predict Tag
description: "是否在预测中自动包含标签", // Whether or not to automatically include tags in predictions
},
},
},
messages: {
loadingModel: "正在加载主动学习模型...", // Loading active learning model...
errorLoadModel: "加载主动学习模型时出错", // Error loading active learning model
saveSuccess: "成功保存了主动学习设置", // Successfully saved active learning settings
},
},
profile: {
settings: "个人资料设置", // Profile Settings
},
errors: {
unknown: {
title: "未知错误", // Unknown Error
message: "该应用程序遇到未知错误。请重试。", // The app encountered an unknown error. Please try again.
},
projectUploadError: {
title: "上传文件时出错", // Error Uploading File
message: "上传文件时出错。请确认文件格式正确,然后重试。",
// There was an error uploading the file. Please verify the file is of the correct format and try again.
},
genericRenderError: {
title: "加载应用程序时出错", // Error Loading Application
message: "呈现应用程序时发生错误。请重试",
// An error occured while rendering the application. Please try again
},
projectInvalidSecurityToken: {
title: "加载项目文件时出错", // Error loading project file
message: "项目引用的安全令牌无效。请验证是否在您的应用程序设置中正确设置了项目的安全令牌",
// The security token referenced by the project is invalid.
// Verify that the security token for the project has been set correctly within your application settings
},
projectInvalidJson: {
title: "解析项目文件时出错", // Error parsing project file
message: "所选的项目文件不包含有效的JSON。请检查该文件, 然后重试。",
// The selected project files does not contain valid JSON. Please check the file any try again.
},
projectDeleteError: {
title: "删除项目时出错", // Error deleting project
message: "删除项目时发生错误。验证项目文件和安全令牌是否存在,然后重试",
// An error occured while deleting the project.
// Validate the project file and security token exist and try again
},
securityTokenNotFound: {
title: "加载项目文件时出错", // Error loading project file
message: "在当前的应用程序设置中找不到该项目引用的安全令牌。验证安全令牌是否存在,然后尝试重新加载项目。",
// The security token referenced by the project cannot be found in your current application settings.
// Verify the security token exists and try to reload the project.
},
canvasError: {
title: "加载画布时出错", // Error loading canvas
message: "加载画布时发生错误,请检查项目的素材,然后重试。",
// There was an error loading the canvas, check the project's assets and try again.
},
importError: {
title: "导入V1项目时出错", // Error importing V1 project
message: "导入V1项目时出错。检查项目文件,然后重试",
// There was an error importing the V1 project. Check the project file and try again
},
pasteRegionTooBigError: {
title: "粘贴区域时出错", // Error pasting region
message: "区域对于该素材过大。请尝试复制其他区域", // Region too big for this asset. Try copying another region
},
exportFormatNotFound: {
title: "导出项目时出错", // Error exporting project
message: "项目缺少导出格式。请在导出设置页面中选择一种导出格式。",
// Project is missing export format. Please select an export format in the export setting page.
},
activeLearningPredictionError: {
title: "主动学习错误", // Active Learning Error
message: "预测当前素材中的区域时发生错误。请验证您的主动学习配置,然后重试",
// An error occurred while predicting regions in the current asset.
// Please verify your active learning configuration and try again
},
},
};
+514
Ver Arquivo
@@ -0,0 +1,514 @@
import { IAppStrings } from "../strings";
/**
* App Strings for Traditional Chinese (zh-tw)
*/
export const chinesetw: IAppStrings = {
appName: "VOTT視覺物件標記工具", // Visual Object Tagging Tool
common: {
displayName: "顯示名稱", // Display Name
description: "說明", // Description
submit: "送出", // Submit
cancel: "取消", // Cancel
save: "儲存", // Save
delete: "刪除", // Delete
provider: "提供者", // Provider
homePage: "首頁", // Home Page
},
titleBar: {
help: "說明", // Help
minimize: "最小化", // Minimize
maximize: "最大化", // Maximize
restore: "還原", // Restore
close: "關閉", // Close
},
homePage: {
newProject: "新專案", // New Project
openLocalProject: {
title: "打開本機專案", // Open Local Project
},
openCloudProject: {
title: "打開雲端專案", // Open Cloud Project
selectConnection: "選擇連線", // Select a Connection
},
recentProjects: "最近的專案", // Recent Projects
deleteProject: {
title: "刪除專案", // Delete Project
confirmation: "確定要刪除專案?", // Are you sure you want to delete project
},
importProject: {
title: "匯入專案", // Import Project
confirmation: "您確定要將專案${project.file.name}的設定轉換為v2格式嗎?我們建議您首先備份專案文件。",
// Are you sure you want to convert project ${project.file.name} project settings to v2 format?
// We recommend you backup the project file first.
},
messages: {
deleteSuccess: "已成功刪除${project.name}專案", // Successfully deleted ${project.name}
},
},
appSettings: {
title: "應用程式設定", // Application Settings
storageTitle: "儲存空間設定", // Storage Settings
uiHelp: "您的設定存放在哪裡", // Where your settings are stored
save: "保存設定", // Save Settings
securityToken: {
name: {
title: "名稱", // Name
},
key: {
title: "鍵", // Key
},
},
securityTokens: {
title: "安全性權杖", // Security Tokens
description: "安全性權杖用於加密專案組態中的敏感資料",
// Security tokens are used to encrypt sensitive data within your project configuration
},
version: {
description: "版本:", // Version:
},
commit: "提交SHA", // Commit SHA
devTools: {
description: "打開應用程式開發工具以幫助診斷問題", // Open application developer tools to help diagnose issues
button: "切換開發工具", // Toggle Developer Tools
},
reload: {
description: "重新載入應用程式,放棄所有目前做的修改", // Reload the app discarding all current changes
button: "重新整理應用程式", // Refresh Application
},
messages: {
saveSuccess: "已成功保存應用程式設定", // Successfully saved application settings
},
},
projectSettings: {
title: "專案設定", // Project Settings
securityToken: {
title: "安全性權杖", // Security Token
description: "用於加密專案檔案中的敏感資料", // Used to encrypt sensitive data within project files
},
save: "保存專案", // Save Project
sourceConnection: {
title: "來源連線", // Source Connection
description: "從何處載入資料", // Where to load assets from
},
targetConnection: {
title: "目標連線", // Target Connection
description: "在哪裡保存專案和匯出的資料", // Where to save the project and exported data
},
videoSettings: {
title: "影片設定", // Video Settings
description: "設定影片標記的速率", // The rate at which frames are extracted for tagging.
frameExtractionRate: "影像取樣率(影像每秒的畫面數)", // Frame Extraction Rate (frames per a video second)
},
addConnection: "新增連線", // Add Connection
messages: {
saveSuccess: "已成功保存${project.name}專案設定", // Successfully saved ${project.name} project settings
},
},
projectMetrics: {
title: "專案相關指標", // Project Metrics
assetsSectionTitle: "圖像數據", // Assets
// As for this VOTT tool, translate "Assets" to "Image data" in Traditional Chinese,
// as "Asset" can be confusing if directly translated.
totalAssetCount: "圖像數據總數", // Total Assets
visitedAssets: "已檢視的圖像數據(${count}", // Visited Assets (${count})
taggedAssets: "已標記的圖像數據(${count}", // Tagged Assets (${count})
nonTaggedAssets: "未標記的圖像數據(${count}", // Not Tagged Assets (${count})
nonVisitedAssets: "未檢視的圖像數據(${count}", // Not Visited Assets (${count})
tagsSectionTitle: "標記和標籤",
// Tags & Labels, so it can actually be same translation to Tags and Labels in Traditional Chinese,
// to differentiate, having slightly different translation for both keywords.
totalRegionCount: "已標記區域總數", // Total Tagged Regions
totalTagCount: "標記總數", // Total Tags
avgTagCountPerAsset: "每個圖像數據的平均標記數", // Average tags per asset
},
tags: {
title: "標記", // Tags
placeholder: "新增標記", // Add new tag
editor: "標記編輯器", // Tags Editor
modal: {
name: "標記名稱", // Tag Name
color: "標記顏色", // Tag Color
},
colors: {
white: "白色", // White
gray: "灰色", // Gray
red: "紅色", // Red
maroon: "栗色", // Maroon
yellow: "黃色", // Yellow
olive: "橄欖", // Olive
lime: "酸橙", // Lime
green: "綠色", // Green
aqua: "水色", // Aqua
teal: "藍綠色", // Teal
blue: "藍色", // Blue
navy: "海軍", // Navy
fuschia: "紫紅色", // Fuschia
purple: "紫色", // Purple
},
warnings: {
existingName: "標記名稱已存在。請選擇其他名字", // Tag name already exists. Choose another name
emptyName: "標記名稱不能為空白", // Cannot have an empty tag name
unknownTagName: "未命名", // Unknown
},
toolbar: {
add: "新增標記", // Add new tag
search: "尋找標記", // Search tags
edit: "編輯標記", // Edit tag
lock: "鎖定標記", // Lock tag
moveUp: "向上移動標記", // Move tag up
moveDown: "向下移動標記", // Move tag down
delete: "刪除標記", // Delete tag
},
},
connections: {
title: "連線", // Connections
details: "連線細節", // Connection Details
settings: "連線設定", // Connection Settings
instructions: "請選擇一個連線進行編輯", // Please select a connection to edit
save: "保存連線", // Save Connection
messages: {
saveSuccess: "已成功儲存${connection.name}", // Successfully saved ${connection.name}
deleteSuccess: "已成功刪除${connection.name}", // Successfully deleted ${connection.name}
},
imageCorsWarning: "警告:在Web瀏覽器中使用VoTT時,由於CORS(跨源資源共享)限制,來自Bing Image Search的某些圖像數據可能無法正確匯出。",
// Warning: When using VoTT in a Web browser, some assets from Bing Image Search may not export correctly
// due to CORS (Cross Origin Resource Sharing) restrictions.
blobCorsWarning: "警告:必須在Azure Blob儲存體帳戶上啟用CORS(跨域資源共享),才能將其用作來源或目標連接。 {0}中提供了有關啟用CORS的更多資訊。",
// Warning: CORS (Cross Domain Resource Sharing) must be enabled on the Azure Blob Storage account,
// in order to use it as a source or target connection.
// More information on enabling CORS can be found in the {0}
azDocLinkText: "Azure說明文件", // Azure Documentation.
providers: {
azureBlob: {
title: "Azure Blob 儲存體", // Azure Blob Storage
description: "",
accountName: {
title: "帳戶名", // Account Name
description: "",
},
containerName: {
title: "容器名稱", // Container Name
description: "",
},
sas: {
title: "SAS", // SAS
description: "用於驗證Blob儲存體帳戶的共用存取簽章",
// Shared access signature used to authenticate to the blob storage account
},
createContainer: {
title: "新增容器", // Create Container
description: "新增blob容器(如果還不存在時)", // Creates the blob container if it does not already exist
},
},
bing: {
title: "Bing 影像搜尋", // Bing Image Search
options: "Bing 影像搜尋選項", // Bing Image Search Options
apiKey: "API密鑰", // API Key
query: "查詢", // Query
aspectRatio: {
title: "長寬比", // Aspect Ratio
all: "所有", // All
square: "矩形", // Square
wide: "寬", // Wide
tall: "高", // Tall
},
},
local: {
title: "本機檔案系統", // Local File System
folderPath: "資料夾路徑", // Folder Path
selectFolder: "選擇資料夾", // Select Folder
chooseFolder: "選取資料夾", // Choose Folder
},
},
},
editorPage: {
width: "寬度", // Width
height: "高度", // Height
tagged: "已標記", // Tagged
visited: "已檢視", // Visited
toolbar: {
select: "選擇 (V)", // Select (V)
pan: "全景", // Pan
drawRectangle: "繪製矩形", // Draw Rectangle
drawPolygon: "繪製多邊形", // Draw Polygon
copyRectangle: "複製矩形", // Copy Rectangle
copy: "複製區域", // Copy Regions
cut: "剪下區域", // Cut Regions
paste: "貼上區域", // Paste Regions
removeAllRegions: "刪除所有區域", // Remove All Regions
previousAsset: "以前的圖像數據", // Previous Asset
nextAsset: "下一個圖像數據", // Next Asset
saveProject: "儲存專案", // Save Project
exportProject: "匯出專案", // Export Project
activeLearning: "主動學習", // Active Learning
},
videoPlayer: {
previousTaggedFrame: {
tooltip: "上一個標記的畫面", // Previous Tagged Frame
},
nextTaggedFrame: {
tooltip: "下一個標記的畫面", // Next Tagged Frame
},
previousExpectedFrame: {
tooltip: "上一個畫面", // Previous Frame
},
nextExpectedFrame: {
tooltip: "下一個畫面", // Next Frame
},
},
help: {
title: "切換輔助說明選單", // Toggle Help Menu
escape: "離開輔助說明選單", // Escape Help Menu
},
assetError: "無法載入圖像數據", // Unable to load asset
tags: {
hotKey: {
apply: "使用快捷鍵來套用標記", // Apply Tag with Hot Key
lock: "用快捷鍵來鎖定標記", // Lock Tag with Hot Key
},
rename: {
title: "重新命名標記", // Rename Tag
confirmation: "您確定要重新命名此標記嗎?它將在所有圖像數據中被重新命名",
// Are you sure you want to rename this tag? It will be renamed throughout all assets
},
delete: {
title: "刪除標記", // Delete Tag
confirmation: "您確定要刪除此標記嗎?它將在所有圖像數據中被刪除,並且只有使用此標記的任何區域也將被刪除",
// Are you sure you want to delete this tag? It will be deleted throughout all assets
// and any regions where this is the only tag will also be deleted
},
},
canvas: {
removeAllRegions: {
title: "刪除所有區域", // Remove All Regions
confirmation: "您確定要刪除所有區域嗎?", // Are you sure you want to remove all regions?
},
},
messages: {
enforceTaggedRegions: {
title: "檢測到無效的區域", // Invalid region(s) detected
description: "一個或多個區域尚未被標記。在繼續下一個圖像數據之前,請確保所有區域均已標記。",
// 1 or more regions have not been tagged.
// Ensure all regions are tagged before continuing to next asset.
},
},
},
export: {
title: "匯出", // Export
settings: "匯出設定", // Export Settings
saveSettings: "儲存匯出設定", // Save Export Settings
providers: {
common: {
properties: {
assetState: {
title: "圖像數據狀態", // Asset State
description: "匯出項目中包括哪些圖像數據", // Which assets to include in the export
options: {
all: "所有圖像數據", // All Assets
visited: "只有已檢視的圖像數據", // Only Visited Assets
tagged: "只有已標記的圖像數據", // Only tagged Assets
},
},
testTrainSplit: {
title: "測試/訓練分割", // Test / Train Split
description: "測試訓練分割以用於匯出數據", // The test train split to use for exported data
},
includeImages: {
title: "包含圖像", // Include Images
description: "是否在目標連接中包括二進位圖像數據",
// Whether or not to include binary image assets in target connection
},
},
},
vottJson: {
displayName: "VoTT JSON", // VoTT JSON
},
azureCV: {
displayName: "Azure自訂視覺服務", // Azure Custom Vision Service
regions: {
// reference to https://azure.microsoft.com/zh-tw/global-infrastructure/geographies/
// for official translation
australiaEast: "澳大利亞東部", // Australia East
centralIndia: "印度中部", // Central India
eastUs: "美國東部", // East US
eastUs2: "美國東部 2", // East US 2
japanEast: "日本東部", // Japan East
northCentralUs: "美國中北部", // North Central US
northEurope: "北歐", // North Europe
southCentralUs: "美國中南部", // South Central US
southeastAsia: "東南亞", // Southeast Asia
ukSouth: "英國南部", // UK South
westUs2: "美國西部 2", // West US 2
westEurope: "西歐", // West Europe
},
properties: {
apiKey: {
title: "API密鑰", // API Key
},
region: {
title: "區域", // Region
description: "部署服務的Azure區域", // The Azure region where your service is deployed
},
classificationType: {
title: "分類類型", // Classification Type
options: {
multiLabel: "每個圖像多個標記", // Multiple tags per image
multiClass: "每個圖像一個標記", // Single tag per image
},
},
name: {
title: "專案名", // Project Name
},
description: {
title: "專案簡介", // Project Description
},
domainId: {
title: "領域", // Domain
},
newOrExisting: {
title: "新增專案或既有專案", // New or Existing Project
options: {
new: "新增專案", // New Project
existing: "既有專案", // Existing Project
},
},
projectId: {
title: "專案名稱", // Project Name
},
projectType: {
title: "專案類型", // Project Type
options: {
classification: "分類", // Classification
objectDetection: "物件偵測", // Object Detection
},
},
},
},
tfRecords: {
displayName: "Tensorflow記錄", // Tensorflow Records
},
pascalVoc: {
displayName: "Pascal VOC", // Pascal VOC
exportUnassigned: {
title: "匯出未指定的項目", // Export Unassigned
description: "是否在已匯出的數據中包括未指定的標記", // Whether or not to include unassigned tags in exported data
},
},
cntk: {
displayName: "Microsoft Cognitive ToolkitCNTK)", // Microsoft Cognitive Toolkit (CNTK)
},
csv: {
displayName: "逗號分隔格式 (CSV)", // Comma Separated Values (CSV)
},
},
messages: {
saveSuccess: "已成功儲存匯出設定", // Successfully saved export settings
},
},
activeLearning: {
title: "主動學習", // Active Learning
form: {
properties: {
modelPathType: {
title: "模型提供者", // Model Provider
description: "從何處載入訓練模型", // Where to load the training model from
options: {
preTrained: "預先訓練Coco SSD", // Pre-trained Coco SSD
customFilePath: "自訂(檔案路徑)", // Custom (File path)
customWebUrl: "自訂 (URL)", // Custom (Url)
},
},
autoDetect: {
title: "自動偵測", // Auto Detect
description: "在圖像數據之間瀏覽時是否自動進行預測",
// Whether or not to automatically make predictions as you navigate between assets
},
modelPath: {
title: "模型路徑", // Model path
description: "從本機檔案系統中選擇模型", // Select a model from your local file system
},
modelUrl: {
title: "模型網址", // Model URL
description: "從公共網址載入模型", // Load your model from a public web URL
},
predictTag: {
title: "預測標記", // Predict Tag
description: "是否在預測中自動包含標記", // Whether or not to automatically include tags in predictions
},
},
},
messages: {
loadingModel: "正在載入主動學習模型...", // Loading active learning model...
errorLoadModel: "載入主動學習模型時出現錯誤", // Error loading active learning model
saveSuccess: "已成功儲存主動學習設定", // Successfully saved active learning settings
},
},
profile: {
settings: "個人資料設定", // Profile Settings
},
errors: {
unknown: {
title: "未知錯誤", // Unknown Error
message: "該應用程式遇到未知錯誤。請再試一遍。", // The app encountered an unknown error. Please try again.
},
projectUploadError: {
title: "上傳檔案時出現錯誤", // Error Uploading File
message: "上傳檔案時出現錯誤。請確認檔案格式正確,然後重試。",
// There was an error uploading the file. Please verify the file is of the correct format and try again.
},
genericRenderError: {
title: "載入應用程序時出現錯誤", // Error Loading Application
message: "轉譯應用程序時發生錯誤。請再試一遍", // An error occured while rendering the application. Please try again
},
projectInvalidSecurityToken: {
title: "載入專案文件時出現錯誤", // Error loading project file
message: "專案使用的安全性認證無效。請驗證是否在您的應用程式設定中正確的設定了專案的安全性認證",
// The security token referenced by the project is invalid. Verify that the security token
// for the project has been set correctly within your application settings
},
projectInvalidJson: {
title: "解析專案文件時出現錯誤", // Error parsing project file
message: "所選擇的專案文件不包含有效的JSON格式。請確認該專案檔案並且重試。",
// The selected project files does not contain valid JSON. Please check the file any try again.
},
projectDeleteError: {
title: "刪除專案時出現錯誤", // Error deleting project
message: "刪除專案時發生錯誤。請確認專案檔案和安全性認證是否存在,然後重試",
// An error occured while deleting the project.
// Validate the project file and security token exist and try again
},
securityTokenNotFound: {
title: "載入專案檔案時出現錯誤", // Error loading project file
message: "在當前的應用程式設定中找不到該專案所使用的安全性認證。請確認安全性認證是否存在,然後嘗試重新載入專案。",
// The security token referenced by the project cannot be found in your current application settings.
// Verify the security token exists and try to reload the project.
},
canvasError: {
title: "載入畫面時出現錯誤", // Error loading canvas
message: "載入畫面時發生錯誤,請檢查專案的圖像數據,然後重試。",
// There was an error loading the canvas, check the project's assets and try again.
},
importError: {
title: "匯入V1格式專案時出現錯誤", // Error importing V1 project
message: "匯入V1格式專案時出現錯誤。請檢查專案檔案,然後重試",
// There was an error importing the V1 project. Check the project file and try again
},
pasteRegionTooBigError: {
title: "貼上區域時發生錯誤", // Error pasting region
message: "此區域對於這一個圖像數據太大了。請嘗試複製其他的區域",
// Region too big for this asset. Try copying another region
},
exportFormatNotFound: {
title: "匯出專案時出現錯誤", // Error exporting project
message: "專案設定中缺少匯出格式。請在匯出設定畫面中選擇一種匯出格式。",
// Project is missing export format. Please select an export format in the export setting page.
},
activeLearningPredictionError: {
title: "主動學習錯誤", // Active Learning Error
message: "在預測當前圖像數據中的區域時發生錯誤。請確認您的主動學習相關設定,然後重試",
// An error occurred while predicting regions in the current asset.
// Please verify your active learning configuration and try again
},
},
};
+10 -10
Ver Arquivo
@@ -818,16 +818,16 @@ export default class MockFactory {
*/
public static projectActions(): IProjectActions {
return {
loadProject: jest.fn(() => Promise.resolve()),
saveProject: jest.fn(() => Promise.resolve()),
loadProject: jest.fn(() => Promise.resolve()) as any,
saveProject: jest.fn(() => Promise.resolve()) as any,
deleteProject: jest.fn(() => Promise.resolve()),
closeProject: jest.fn(() => Promise.resolve()),
loadAssets: jest.fn(() => Promise.resolve()),
loadAssets: jest.fn(() => Promise.resolve()) as any,
exportProject: jest.fn(() => Promise.resolve()),
loadAssetMetadata: jest.fn(() => Promise.resolve()),
saveAssetMetadata: jest.fn(() => Promise.resolve()),
updateProjectTag: jest.fn(() => Promise.resolve()),
deleteProjectTag: jest.fn(() => Promise.resolve()),
loadAssetMetadata: jest.fn(() => Promise.resolve()) as any,
saveAssetMetadata: jest.fn(() => Promise.resolve()) as any,
updateProjectTag: jest.fn(() => Promise.resolve()) as any,
deleteProjectTag: jest.fn(() => Promise.resolve()) as any,
};
}
@@ -836,8 +836,8 @@ export default class MockFactory {
*/
public static connectionActions(): IConnectionActions {
return {
loadConnection: jest.fn((connection: IConnection) => Promise.resolve()),
saveConnection: jest.fn((connection: IConnection) => Promise.resolve()),
loadConnection: jest.fn((connection: IConnection) => Promise.resolve()) as any,
saveConnection: jest.fn((connection: IConnection) => Promise.resolve()) as any,
deleteConnection: jest.fn((connection: IConnection) => Promise.resolve()),
};
}
@@ -1036,7 +1036,7 @@ export default class MockFactory {
}
public static mockElement(assetTestCache: Map<string, IAsset>) {
document.createElement = jest.fn((elementType) => {
document.createElement = jest.fn((elementType: string) => {
switch (elementType) {
case "img":
const mockImage = MockFactory.mockImage(assetTestCache);
+16 -1
Ver Arquivo
@@ -1,8 +1,19 @@
import { strings, addLocValues, IAppStrings, interpolate, interpolateJson } from "./strings";
import { english } from "./localization/en-us";
import { spanish } from "./localization/es-cl";
import { japanese } from "./localization/ja";
import { chinesetw } from "./localization/zh-tw";
import { korean } from "./localization/ko-kr";
import { chinese } from "./localization/zh-ch";
const languages = ["en", "es"];
const languages = [
"en",
"es",
"ja",
"tw",
"ko",
"ch",
];
describe("Localization tests", () => {
@@ -10,6 +21,10 @@ describe("Localization tests", () => {
return {
en: english,
es: spanish,
ja: japanese,
tw: chinesetw,
ko: korean,
ch: chinese,
}[language];
}
+9
Ver Arquivo
@@ -1,6 +1,10 @@
import LocalizedStrings, { LocalizedStringsMethods } from "react-localization";
import { english } from "./localization/en-us";
import { spanish } from "./localization/es-cl";
import { japanese } from "./localization/ja";
import { chinesetw } from "./localization/zh-tw";
import { korean } from "./localization/ko-kr";
import { chinese } from "./localization/zh-ch";
/**
* Interface for all required strings in application
@@ -452,8 +456,13 @@ interface IErrorMetadata {
interface IStrings extends LocalizedStringsMethods, IAppStrings { }
export const strings: IStrings = new LocalizedStrings({
// TODO: Need to comment out other languages which will not be used
en: english,
es: spanish,
ja: japanese,
tw: chinesetw,
ko: korean,
ch: chinese,
});
/**
+3 -3
Ver Arquivo
@@ -72,7 +72,7 @@ function registerContextMenu(browserWindow: BrowserWindow): void {
const selectionMenu = Menu.buildFromTemplate([
{ role: "copy", accelerator: "CmdOrCtrl+C" },
{ type: "separator" },
{ role: "selectall", accelerator: "CmdOrCtrl+A" },
{ role: "selectAll", accelerator: "CmdOrCtrl+A" },
]);
const inputMenu = Menu.buildFromTemplate([
@@ -83,7 +83,7 @@ function registerContextMenu(browserWindow: BrowserWindow): void {
{ role: "copy", accelerator: "CmdOrCtrl+C" },
{ role: "paste", accelerator: "CmdOrCtrl+V" },
{ type: "separator" },
{ role: "selectall", accelerator: "CmdOrCtrl+A" },
{ role: "selectAll", accelerator: "CmdOrCtrl+A" },
]);
browserWindow.webContents.on("context-menu", (e, props) => {
@@ -111,7 +111,7 @@ function registerContextMenu(browserWindow: BrowserWindow): void {
{ role: "reload" },
{ type: "separator" },
{ role: "toggleDevTools" },
{ role: "toggleFullScreen" },
{ role: "togglefullscreen" },
{ type: "separator" },
{ role: "resetZoom" },
{ role: "zoomIn" },
@@ -5,7 +5,7 @@ import LocalFileSystem from "./localFileSystem";
jest.mock("electron", () => ({
dialog: {
showOpenDialog: jest.fn(),
showOpenDialogSync: jest.fn(),
},
}));
import { dialog } from "electron";
@@ -72,7 +72,7 @@ describe("LocalFileSystem Storage Provider", () => {
it("selectContainer opens a dialog and resolves with selected path", async () => {
const expectedContainerPath = "/path/to/container";
const mockMethod = dialog.showOpenDialog as jest.Mock;
const mockMethod = dialog.showOpenDialogSync as jest.Mock;
mockMethod.mockReturnValue([expectedContainerPath]);
const result = await localFileSystem.selectContainer();
@@ -80,7 +80,7 @@ describe("LocalFileSystem Storage Provider", () => {
});
it("selectContainer rejects when a folder path is not returned", async () => {
const mockMethod = dialog.showOpenDialog as jest.Mock;
const mockMethod = dialog.showOpenDialogSync as jest.Mock;
mockMethod.mockReturnValue([]);
await expect(localFileSystem.selectContainer()).rejects.not.toBeNull();
@@ -14,7 +14,7 @@ export default class LocalFileSystem implements IStorageProvider {
public selectContainer(): Promise<string> {
return new Promise<string>((resolve, reject) => {
const filePaths = dialog.showOpenDialog(this.browserWindow, {
const filePaths = dialog.showOpenDialogSync(this.browserWindow, {
title: strings.connections.providers.local.selectFolder,
buttonLabel: strings.connections.providers.local.chooseFolder,
properties: ["openDirectory", "createDirectory"],
-2
Ver Arquivo
@@ -22,12 +22,10 @@ input[type=file] {
box-shadow: inset 0 0 6px $darker-3;
background-color: $lighter-1;
border-radius: 10px;
border-radius: 10px;
}
/* Handle */
::-webkit-scrollbar-thumb {
border-radius: 10px;
border-radius: 10px;
background: $lighter-4;
box-shadow: 0 0 6px $darker-3;
@@ -17,9 +17,7 @@ describe("Azure Custom Vision Service", () => {
apiKey: "ABC123",
};
customVisionService = new AzureCustomVisionService(customVisionOptions);
});
beforeAll(() => {
axios.get = jest.fn();
axios.post = jest.fn();
getMock = axios.get as jest.Mock;
@@ -70,13 +68,12 @@ describe("Azure Custom Vision Service", () => {
});
});
await expect(customVisionService.create(testProject)).rejects.not.toBeNull();
expect(axios.post).toBeCalledWith(
expect.stringContaining(`${customVisionOptions.baseUrl}/projects?`),
null,
expect.anything(),
);
await expect(customVisionService.create(testProject)).rejects.not.toBeNull();
});
});
+31 -3
Ver Arquivo
@@ -115,9 +115,37 @@ describe("CNTK Export Provider", () => {
const assetsToExport = await getAssetsSpy.mock.results[0].value;
const testSplit = (100 - (defaultOptions.testTrainSplit || 80)) / 100;
const testCount = Math.ceil(assetsToExport.length * testSplit);
const testArray = assetsToExport.slice(0, testCount);
const trainArray = assetsToExport.slice(testCount, assetsToExport.length);
const trainArray = [];
const testArray = [];
const tagsAssestList: {
[index: string]: {
assetSet: Set<string>,
testArray: string[],
trainArray: string[],
},
} = {};
testProject.tags.forEach((tag) =>
tagsAssestList[tag.name] = {
assetSet: new Set(), testArray: [],
trainArray: [],
});
assetsToExport.forEach((assetMetadata) => {
assetMetadata.regions.forEach((region) => {
region.tags.forEach((tagName) => {
if (tagsAssestList[tagName]) {
tagsAssestList[tagName].assetSet.add(assetMetadata.asset.name);
}
});
});
});
for (const tagKey of Object.keys(tagsAssestList)) {
const assetSet = tagsAssestList[tagKey].assetSet;
const testCount = Math.ceil(assetSet.size * testSplit);
testArray.push(...Array.from(assetSet).slice(0, testCount));
trainArray.push(...Array.from(assetSet).slice(testCount, assetSet.size));
}
const storageProviderMock = LocalFileSystemProxy as any;
const writeBinaryCalls = storageProviderMock.mock.instances[0].writeBinary.mock.calls;
+8 -3
Ver Arquivo
@@ -3,6 +3,7 @@ import { ExportProvider, IExportResults } from "./exportProvider";
import { IAssetMetadata, IExportProviderOptions, IProject } from "../../models/applicationState";
import HtmlFileReader from "../../common/htmlFileReader";
import Guard from "../../common/guard";
import { splitTestAsset } from "./testAssetsSplitHelper";
enum ExportSplit {
Test,
@@ -33,13 +34,17 @@ export class CntkExportProvider extends ExportProvider<ICntkExportProviderOption
public async export(): Promise<IExportResults> {
await this.createFolderStructure();
const assetsToExport = await this.getAssetsForExport();
const testAssets: string[] = [];
const testSplit = (100 - (this.options.testTrainSplit || 80)) / 100;
const testCount = Math.ceil(assetsToExport.length * testSplit);
const testArray = assetsToExport.slice(0, testCount);
if (testSplit > 0 && testSplit <= 1) {
const splittedAssets = splitTestAsset(assetsToExport, this.project.tags, testSplit);
testAssets.push(...splittedAssets);
}
const results = await assetsToExport.mapAsync(async (assetMetadata) => {
try {
const exportSplit = testArray.find((am) => am.asset.id === assetMetadata.asset.id)
const exportSplit = testAssets.find((am) => am === assetMetadata.asset.id)
? ExportSplit.Test
: ExportSplit.Train;
+1 -3
Ver Arquivo
@@ -42,7 +42,7 @@ describe("CSV Format Export Provider", () => {
const expectedFileName = "vott-csv-export/" + testProject.name.replace(" ", "-") + "-export.csv";
beforeAll(() => {
beforeEach(() => {
HtmlFileReader.getAssetBlob = jest.fn(() => {
return Promise.resolve(new Blob(["Some binary data"]));
});
@@ -52,9 +52,7 @@ describe("CSV Format Export Provider", () => {
getAssets: jest.fn(() => Promise.resolve(testAssets)),
};
});
});
beforeEach(() => {
registerProviders();
});
+2 -2
Ver Arquivo
@@ -14,7 +14,7 @@ describe("Export Provider Base", () => {
let testProject: IProject = null;
const testAssets = MockFactory.createTestAssets(10, 1);
beforeAll(() => {
beforeEach(() => {
AssetProviderFactory.create = jest.fn(() => {
return {
getAssets: jest.fn(() => Promise.resolve(testAssets)),
@@ -26,7 +26,7 @@ describe("Export Provider Base", () => {
asset: { ...asset },
regions: [],
};
});
}) as any;
testProject = {
...MockFactory.createTestProject("TestProject"),
+58 -15
Ver Arquivo
@@ -34,19 +34,17 @@ describe("PascalVOC Json Export Provider", () => {
const tagLengthInPbtxt = 31;
HtmlFileReader.getAssetArray = jest.fn(() => {
return Promise.resolve(new Uint8Array([1, 2, 3]).buffer);
});
beforeEach(() => {
HtmlFileReader.getAssetArray = jest.fn(() => {
return Promise.resolve(new Uint8Array([1, 2, 3]).buffer);
});
beforeAll(() => {
AssetProviderFactory.create = jest.fn(() => {
return {
getAssets: jest.fn(() => Promise.resolve(testAssets)),
};
});
});
beforeEach(() => {
registerProviders();
});
@@ -69,7 +67,9 @@ describe("PascalVOC Json Export Provider", () => {
beforeEach(() => {
const assetServiceMock = AssetService as jest.Mocked<typeof AssetService>;
assetServiceMock.prototype.getAssetMetadata = jest.fn((asset) => {
const mockTag = MockFactory.createTestTag();
const mockTag1 = MockFactory.createTestTag("1");
const mockTag2 = MockFactory.createTestTag("2");
const mockTag = Number(asset.id.split("-")[1]) > 7 ? mockTag1 : mockTag2;
const mockRegion1 = MockFactory.createTestRegion("region-1", [mockTag.name]);
const mockRegion2 = MockFactory.createTestRegion("region-2", [mockTag.name]);
@@ -352,27 +352,70 @@ describe("PascalVOC Json Export Provider", () => {
};
const testProject = { ...baseTestProject };
const testAssets = MockFactory.createTestAssets(10, 0);
const testAssets = MockFactory.createTestAssets(13, 0);
testAssets.forEach((asset) => asset.state = AssetState.Tagged);
testProject.assets = _.keyBy(testAssets, (asset) => asset.id);
testProject.tags = [MockFactory.createTestTag("1")];
testProject.tags = MockFactory.createTestTags(3);
const exportProvider = new PascalVOCExportProvider(testProject, options);
const getAssetsSpy = jest.spyOn(exportProvider, "getAssetsForExport");
await exportProvider.export();
const storageProviderMock = LocalFileSystemProxy as any;
const writeTextFileCalls = storageProviderMock.mock.instances[0].writeText.mock.calls as any[];
const valDataIndex = writeTextFileCalls
const valDataIndex1 = writeTextFileCalls
.findIndex((args) => args[0].endsWith("/ImageSets/Main/Tag 1_val.txt"));
const trainDataIndex = writeTextFileCalls
const trainDataIndex1 = writeTextFileCalls
.findIndex((args) => args[0].endsWith("/ImageSets/Main/Tag 1_train.txt"));
const valDataIndex2 = writeTextFileCalls
.findIndex((args) => args[0].endsWith("/ImageSets/Main/Tag 2_val.txt"));
const trainDataIndex2 = writeTextFileCalls
.findIndex((args) => args[0].endsWith("/ImageSets/Main/Tag 2_train.txt"));
const expectedTrainCount = (testTrainSplit / 100) * testAssets.length;
const expectedTestCount = ((100 - testTrainSplit) / 100) * testAssets.length;
const assetsToExport = await getAssetsSpy.mock.results[0].value;
const trainArray = [];
const testArray = [];
const tagsAssestList: {
[index: string]: {
assetSet: Set<string>,
testArray: string[],
trainArray: string[],
},
} = {};
testProject.tags.forEach((tag) =>
tagsAssestList[tag.name] = {
assetSet: new Set(), testArray: [],
trainArray: [],
});
assetsToExport.forEach((assetMetadata) => {
assetMetadata.regions.forEach((region) => {
region.tags.forEach((tagName) => {
if (tagsAssestList[tagName]) {
tagsAssestList[tagName].assetSet.add(assetMetadata.asset.name);
}
});
});
});
expect(writeTextFileCalls[valDataIndex][1].split("\n")).toHaveLength(expectedTestCount);
expect(writeTextFileCalls[trainDataIndex][1].split("\n")).toHaveLength(expectedTrainCount);
for (const tagKey of Object.keys(tagsAssestList)) {
const assetSet = tagsAssestList[tagKey].assetSet;
const testCount = Math.ceil(((100 - testTrainSplit) / 100) * assetSet.size);
tagsAssestList[tagKey].testArray = Array.from(assetSet).slice(0, testCount);
tagsAssestList[tagKey].trainArray = Array.from(assetSet).slice(testCount, assetSet.size);
testArray.push(...tagsAssestList[tagKey].testArray);
trainArray.push(...tagsAssestList[tagKey].trainArray);
}
expect(writeTextFileCalls[valDataIndex1][1].split(/\r?\n/).filter((line) =>
line.endsWith(" 1"))).toHaveLength(tagsAssestList["Tag 1"].testArray.length);
expect(writeTextFileCalls[trainDataIndex1][1].split(/\r?\n/).filter((line) =>
line.endsWith(" 1"))).toHaveLength(tagsAssestList["Tag 1"].trainArray.length);
expect(writeTextFileCalls[valDataIndex2][1].split(/\r?\n/).filter((line) =>
line.endsWith(" 1"))).toHaveLength(tagsAssestList["Tag 2"].testArray.length);
expect(writeTextFileCalls[trainDataIndex2][1].split(/\r?\n/).filter((line) =>
line.endsWith(" 1"))).toHaveLength(tagsAssestList["Tag 2"].trainArray.length);
}
it("Correctly generated files based on 50/50 test / train split", async () => {
+43 -24
Ver Arquivo
@@ -6,6 +6,7 @@ import HtmlFileReader from "../../common/htmlFileReader";
import { itemTemplate, annotationTemplate, objectTemplate } from "./pascalVOC/pascalVOCTemplates";
import { interpolate } from "../../common/strings";
import os from "os";
import { splitTestAsset } from "./testAssetsSplitHelper";
interface IObjectInfo {
name: string;
@@ -253,40 +254,58 @@ export class PascalVOCExportProvider extends ExportProvider<IPascalVOCExportProv
}
});
// Save ImageSets
await tags.forEachAsync(async (tag) => {
const tagInstances = tagUsage.get(tag.name) || 0;
if (!exportUnassignedTags && tagInstances === 0) {
return;
}
if (testSplit > 0 && testSplit <= 1) {
const tags = this.project.tags;
const testAssets: string[] = splitTestAsset(allAssets, tags, testSplit);
const assetList = [];
assetUsage.forEach((tags, assetName) => {
if (tags.has(tag.name)) {
assetList.push(`${assetName} 1`);
} else {
assetList.push(`${assetName} -1`);
await tags.forEachAsync(async (tag) => {
const tagInstances = tagUsage.get(tag.name) || 0;
if (!exportUnassignedTags && tagInstances === 0) {
return;
}
});
if (testSplit > 0 && testSplit <= 1) {
// Split in Test and Train sets
const totalAssets = assetUsage.size;
const testCount = Math.ceil(totalAssets * testSplit);
const testArray = assetList.slice(0, testCount);
const trainArray = assetList.slice(testCount, totalAssets);
const testArray = [];
const trainArray = [];
assetUsage.forEach((tags, assetName) => {
let assetString = "";
if (tags.has(tag.name)) {
assetString = `${assetName} 1`;
} else {
assetString = `${assetName} -1`;
}
if (testAssets.find((am) => am === assetName)) {
testArray.push(assetString);
} else {
trainArray.push(assetString);
}
});
const testImageSetFileName = `${imageSetsMainFolderName}/${tag.name}_val.txt`;
await this.storageProvider.writeText(testImageSetFileName, testArray.join(os.EOL));
const trainImageSetFileName = `${imageSetsMainFolderName}/${tag.name}_train.txt`;
await this.storageProvider.writeText(trainImageSetFileName, trainArray.join(os.EOL));
});
} else {
// Save ImageSets
await tags.forEachAsync(async (tag) => {
const tagInstances = tagUsage.get(tag.name) || 0;
if (!exportUnassignedTags && tagInstances === 0) {
return;
}
const assetList = [];
assetUsage.forEach((tags, assetName) => {
if (tags.has(tag.name)) {
assetList.push(`${assetName} 1`);
} else {
assetList.push(`${assetName} -1`);
}
});
} else {
const imageSetFileName = `${imageSetsMainFolderName}/${tag.name}.txt`;
await this.storageProvider.writeText(imageSetFileName, assetList.join(os.EOL));
}
});
});
}
}
}
+2 -4
Ver Arquivo
@@ -35,17 +35,15 @@ describe("TFRecords Json Export Provider", () => {
const tagLengthInPbtxt = 31;
HtmlFileReader.getAssetArray = jest.fn(() => Promise.resolve(new Uint8Array([1, 2, 3]).buffer));
beforeEach(() => {
HtmlFileReader.getAssetArray = jest.fn(() => Promise.resolve(new Uint8Array([1, 2, 3]).buffer));
beforeAll(() => {
AssetProviderFactory.create = jest.fn(() => {
return {
getAssets: jest.fn(() => Promise.resolve(testAssets)),
};
});
});
beforeEach(() => {
registerProviders();
});
@@ -0,0 +1,61 @@
import _ from "lodash";
import {
IAssetMetadata, AssetState, IRegion,
RegionType, IPoint, IExportProviderOptions,
} from "../../models/applicationState";
import MockFactory from "../../common/mockFactory";
import { splitTestAsset } from "./testAssetsSplitHelper";
import { appInfo } from "../../common/appInfo";
describe("splitTestAsset Helper tests", () => {
describe("Test Train Splits", () => {
async function testTestTrainSplit(testTrainSplit: number): Promise<void> {
const assetArray = MockFactory.createTestAssets(13, 0);
const tags = MockFactory.createTestTags(2);
assetArray.forEach((asset) => asset.state = AssetState.Tagged);
const testSplit = (100 - testTrainSplit) / 100;
const testCount = Math.ceil(testSplit * assetArray.length);
const assetMetadatas = assetArray.map((asset, i) =>
MockFactory.createTestAssetMetadata(asset,
i < (assetArray.length - testCount) ?
[MockFactory.createTestRegion("Region" + i, [tags[0].name])] :
[MockFactory.createTestRegion("Region" + i, [tags[1].name])]));
const testAssetsNames = splitTestAsset(assetMetadatas, tags, testSplit);
const trainAssetsArray = assetMetadatas.filter((assetMetadata) =>
testAssetsNames.indexOf(assetMetadata.asset.name) < 0);
const testAssetsArray = assetMetadatas.filter((assetMetadata) =>
testAssetsNames.indexOf(assetMetadata.asset.name) >= 0);
const expectedTestCount = Math.ceil(testSplit * testCount) +
Math.ceil(testSplit * (assetArray.length - testCount));
expect(testAssetsNames).toHaveLength(expectedTestCount);
expect(trainAssetsArray.length + testAssetsArray.length).toEqual(assetMetadatas.length);
expect(testAssetsArray).toHaveLength(expectedTestCount);
expect(testAssetsArray.filter((assetMetadata) => assetMetadata.regions[0].tags[0] === tags[0].name).length)
.toBeGreaterThan(0);
expect(testAssetsArray.filter((assetMetadata) => assetMetadata.regions[0].tags[0] === tags[1].name).length)
.toBeGreaterThan(0);
}
it("Correctly generated files based on 50/50 test / train split", async () => {
await testTestTrainSplit(50);
});
it("Correctly generated files based on 60/40 test / train split", async () => {
await testTestTrainSplit(60);
});
it("Correctly generated files based on 80/20 test / train split", async () => {
await testTestTrainSplit(80);
});
it("Correctly generated files based on 90/10 test / train split", async () => {
await testTestTrainSplit(90);
});
});
});
@@ -0,0 +1,30 @@
import { IAssetMetadata, ITag } from "../../models/applicationState";
/**
* A helper function to split train and test assets
* @param template String containing variables
* @param params Params containing substitution values
*/
export function splitTestAsset(allAssets: IAssetMetadata[], tags: ITag[], testSplitRatio: number): string[] {
if (testSplitRatio <= 0 || testSplitRatio > 1) { return []; }
const testAssets: string[] = [];
const tagsAssetDict: { [index: string]: { assetList: Set<string> } } = {};
tags.forEach((tag) => tagsAssetDict[tag.name] = { assetList: new Set() });
allAssets.forEach((assetMetadata) => {
assetMetadata.regions.forEach((region) => {
region.tags.forEach((tagName) => {
if (tagsAssetDict[tagName]) {
tagsAssetDict[tagName].assetList.add(assetMetadata.asset.name);
}
});
});
});
for (const tagKey of Object.keys(tagsAssetDict)) {
const assetList = tagsAssetDict[tagKey].assetList;
const testCount = Math.ceil(assetList.size * testSplitRatio);
testAssets.push(...Array.from(assetList).slice(0, testCount));
}
return testAssets;
}
+1 -3
Ver Arquivo
@@ -39,7 +39,7 @@ describe("VoTT Json Export Provider", () => {
const expectedFileName = "vott-json-export/" + testProject.name.replace(" ", "-") + constants.exportFileExtension;
beforeAll(() => {
beforeEach(() => {
HtmlFileReader.getAssetBlob = jest.fn(() => {
return Promise.resolve(new Blob(["Some binary data"]));
});
@@ -49,9 +49,7 @@ describe("VoTT Json Export Provider", () => {
getAssets: jest.fn(() => Promise.resolve(testAssets)),
};
});
});
beforeEach(() => {
registerProviders();
});
+1 -1
Ver Arquivo
@@ -89,7 +89,7 @@ export class AssetProviderFactory {
* @param connection - Connection for an Asset Provider
*/
public static createFromConnection(connection: IConnection): IAssetProvider {
return this.create(connection.providerType, connection.providerOptions);
return AssetProviderFactory.create(connection.providerType, connection.providerOptions);
}
/**
+24 -18
Ver Arquivo
@@ -8,30 +8,36 @@ import { AssetService } from "../../services/assetService";
import { AssetType } from "../../models/applicationState";
describe("Azure blob functions", () => {
const ad = MockFactory.createAzureData();
const options = ad.options;
const serviceURL = ServiceURL as jest.Mocked<typeof ServiceURL>;
serviceURL.prototype.listContainersSegment = jest.fn(() => Promise.resolve(ad.containers));
ContainerURL.fromServiceURL = jest.fn(() => new ContainerURL(null, null));
const containerURL = ContainerURL as jest.Mocked<typeof ContainerURL>;
containerURL.prototype.create = jest.fn(() => Promise.resolve({ statusCode: 201 }));
containerURL.prototype.delete = jest.fn(() => Promise.resolve({ statusCode: 204 }));
containerURL.prototype.listBlobFlatSegment = jest.fn(() => Promise.resolve(ad.blobs));
BlockBlobURL.fromContainerURL = jest.fn(() => new BlockBlobURL(null, null));
let ad = null;
let options = null;
let serviceURL = null;
let containerURL = null;
registerProviders();
beforeEach(() => {
ad = MockFactory.createAzureData();
options = ad.options;
serviceURL = ServiceURL as jest.Mocked<typeof ServiceURL>;
serviceURL.prototype.listContainersSegment = jest.fn(() => Promise.resolve(ad.containers)) as any;
ContainerURL.fromServiceURL = jest.fn(() => new ContainerURL(null, null));
containerURL = ContainerURL as jest.Mocked<typeof ContainerURL>;
containerURL.prototype.create = jest.fn(() => Promise.resolve({ statusCode: 201 })) as any;
containerURL.prototype.delete = jest.fn(() => Promise.resolve({ statusCode: 204 })) as any;
containerURL.prototype.listBlobFlatSegment = jest.fn(() => Promise.resolve(ad.blobs)) as any;
BlockBlobURL.fromContainerURL = jest.fn(() => new BlockBlobURL(null, null));
});
it("Reads text from a blob", async () => {
const blockBlobURL = BlockBlobURL as jest.Mocked<typeof BlockBlobURL>;
const blob = MockFactory.blob(ad.blobName, ad.blobText, ad.fileType);
blockBlobURL.prototype.download = jest.fn(() => Promise.resolve({
blobBody: Promise.resolve(blob),
}));
})) as any;
const provider: AzureBlobStorage = new AzureBlobStorage(options);
@@ -55,7 +61,7 @@ describe("Azure blob functions", () => {
);
blockBlobURL.prototype.download = jest.fn(() => Promise.resolve({
blobBody: Promise.resolve(blob),
}));
})) as any;
const provider: AzureBlobStorage = new AzureBlobStorage(options);
@@ -76,7 +82,7 @@ describe("Azure blob functions", () => {
const blob = MockFactory.blob(ad.blobName, ad.blobText, ad.fileType);
blockBlobURL.prototype.download = jest.fn(() => Promise.resolve({
blobBody: Promise.resolve(blob),
}));
})) as any;
const provider: AzureBlobStorage = new AzureBlobStorage(options);
@@ -188,7 +194,7 @@ describe("Azure blob functions", () => {
return {
type: AssetType.Image,
};
});
}) as any;
provider.getFileName = jest.fn();
const assets = await provider.getAssets();
expect(provider.getFileName).toBeCalled();
+8 -6
Ver Arquivo
@@ -18,12 +18,14 @@ describe("Bing Image Search", () => {
{ contentUrl: "http://images.com/image4.jpg" },
];
axios.get = jest.fn(() => {
return Promise.resolve({
data: {
value: assets,
},
});
beforeEach(() => {
axios.get = jest.fn(() => {
return Promise.resolve({
data: {
value: assets,
},
});
}) as any;
});
it("calls the Bing image search API", async () => {
@@ -27,7 +27,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
it("selectContainer", async () => {
const expectedFolderPath = "/test";
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFolderPath));
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFolderPath)) as any;
const actualFolderPath = await provider.selectContainer();
expect(IpcRendererProxy.send).toBeCalledWith("LocalFileSystem:selectContainer");
@@ -35,7 +35,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
});
it("writeText", async () => {
IpcRendererProxy.send = jest.fn(() => Promise.resolve());
IpcRendererProxy.send = jest.fn(() => Promise.resolve()) as any;
const fileName = "test.txt";
const contents = "Hello World!";
@@ -47,7 +47,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
it("readText", async () => {
const expectedContents = "Hello World!";
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedContents));
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedContents)) as any;
const fileName = "test.txt";
const expectedFilePath = [options.folderPath, fileName].join("/");
@@ -58,7 +58,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
});
it("deleteFile", async () => {
IpcRendererProxy.send = jest.fn(() => Promise.resolve());
IpcRendererProxy.send = jest.fn(() => Promise.resolve()) as any;
const fileName = "test.txt";
const expectedFilePath = [options.folderPath, fileName].join("/");
@@ -68,7 +68,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
});
it("createContainer", async () => {
IpcRendererProxy.send = jest.fn(() => Promise.resolve());
IpcRendererProxy.send = jest.fn(() => Promise.resolve()) as any;
const containerName = "test";
const expectedFolderPath = [options.folderPath, containerName].join("/");
@@ -78,7 +78,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
});
it("deleteContainer", async () => {
IpcRendererProxy.send = jest.fn(() => Promise.resolve());
IpcRendererProxy.send = jest.fn(() => Promise.resolve()) as any;
const containerName = "test";
const expectedContainerPath = [options.folderPath, containerName].join("/");
@@ -95,7 +95,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
"/test/file4.txt",
];
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFiles));
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFiles)) as any;
const containerName = "test";
const expectedContainerPath = [options.folderPath, containerName].join("/");
@@ -113,7 +113,7 @@ describe("LocalFileSystem Proxy Storage Provider", () => {
"/test/folder4",
];
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFolders));
IpcRendererProxy.send = jest.fn(() => Promise.resolve(expectedFolders)) as any;
const containerName = "test";
const expectedContainerPath = [options.folderPath, containerName].join("/");
+1 -1
Ver Arquivo
@@ -101,7 +101,7 @@ export class StorageProviderFactory {
* @param connection Connection for a Storage Provider
*/
public static createFromConnection(connection: IConnection) {
return this.create(connection.providerType, connection.providerOptions);
return StorageProviderFactory.create(connection.providerType, connection.providerOptions);
}
/**
+14 -9
Ver Arquivo
@@ -3,25 +3,30 @@ import { mount, ReactWrapper } from "enzyme";
import Alert, { IAlertProps, IAlertState } from "./alert";
describe("Alert component", () => {
const modalCloseHandler = jest.fn();
const defaultProps: IAlertProps = {
title: "Test Title",
message: "Test Message",
onClose: modalCloseHandler,
show: false,
};
let modalCloseHandler = null;
let defaultProps: IAlertProps = null;
function createComponent(props: IAlertProps): ReactWrapper<IAlertProps, IAlertState, Alert> {
return mount(<Alert {...props}></Alert>);
}
beforeEach(() => {
modalCloseHandler = jest.fn();
defaultProps = {
title: "Test Title",
message: "Test Message",
onClose: modalCloseHandler,
show: false,
};
});
it("Is defined", () => {
expect(Alert).toBeDefined();
});
it("Renders nothing if not activated", () => {
const wrapper = createComponent(defaultProps);
expect(wrapper.html()).toBeNull();
expect(wrapper.html()).toEqual("");
});
it("Renders modal when activated", () => {
@@ -52,7 +57,7 @@ describe("Alert component", () => {
});
it("Calls onClose handler when clicking positive button", () => {
const arg = {value: "test"};
const arg = { value: "test" };
const wrapper = createComponent(defaultProps);
wrapper.instance().open(arg);
@@ -11,29 +11,15 @@ describe("Asset Preview Component", () => {
let wrapper: ReactWrapper<IAssetPreviewProps, IAssetPreviewState> = null;
// tslint:disable-next-line:max-line-length
const dataUri = "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7";
const onLoadedHandler = jest.fn();
const onErrorHandler = jest.fn();
const onActivatedHandler = jest.fn();
const onDeactivatedHandler = jest.fn();
const onChildAssetSelectedHandler = jest.fn();
const onAssetChangedHandler = jest.fn();
const onBeforeAssetChangedHandler = jest.fn(() => true);
let onLoadedHandler = null;
let onErrorHandler = null;
let onActivatedHandler = null;
let onDeactivatedHandler = null;
let onChildAssetSelectedHandler = null;
let onAssetChangedHandler = null;
let onBeforeAssetChangedHandler = null;
const defaultProps: IAssetPreviewProps = {
asset: {
...MockFactory.createTestAsset("test-image-asset"),
path: dataUri,
},
autoPlay: false,
controlsEnabled: true,
onLoaded: onLoadedHandler,
onError: onErrorHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onBeforeAssetChanged: onBeforeAssetChangedHandler,
onAssetChanged: onAssetChangedHandler,
onChildAssetSelected: onChildAssetSelectedHandler,
};
let defaultProps: IAssetPreviewProps = null;
function createComponent(props?: IAssetPreviewProps): ReactWrapper<IAssetPreviewProps, IAssetPreviewState> {
props = props || defaultProps;
@@ -41,6 +27,31 @@ describe("Asset Preview Component", () => {
}
beforeEach(() => {
onLoadedHandler = jest.fn();
onErrorHandler = jest.fn();
onActivatedHandler = jest.fn();
onDeactivatedHandler = jest.fn();
onChildAssetSelectedHandler = jest.fn();
onAssetChangedHandler = jest.fn();
onBeforeAssetChangedHandler = jest.fn(() => true);
defaultProps = {
asset: {
...MockFactory.createTestAsset("test-image-asset"),
path: dataUri,
},
autoPlay: false,
controlsEnabled: true,
onLoaded: onLoadedHandler,
onError: onErrorHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onBeforeAssetChanged: onBeforeAssetChangedHandler,
onAssetChanged: onAssetChangedHandler,
onChildAssetSelected: onChildAssetSelectedHandler,
};
onLoadedHandler.mockClear();
onErrorHandler.mockClear();
onActivatedHandler.mockClear();
@@ -14,13 +14,21 @@ describe("TFRecord Asset Component", () => {
let wrapper: ReactWrapper<IAssetProps> = null;
const onLoadHandler = jest.fn();
const onActivatedHandler = jest.fn();
const onDeactivatedHandler = jest.fn();
const onErrorHandler = jest.fn();
let onLoadHandler = null;
let onActivatedHandler = null;
let onDeactivatedHandler = null;
let onErrorHandler = null;
let tfRecords: Buffer;
let defaultProps: IAssetProps = null;
beforeEach(() => {
onLoadHandler = jest.fn();
onActivatedHandler = jest.fn();
onDeactivatedHandler = jest.fn();
onErrorHandler = jest.fn();
let builder: TFRecordsBuilder;
builder = new TFRecordsBuilder();
builder.addFeature("image/encoded", FeatureType.Binary, dataImage);
@@ -30,22 +38,22 @@ describe("TFRecord Asset Component", () => {
onLoadHandler.mockClear();
onErrorHandler.mockClear();
});
HtmlFileReader.getAssetArray = jest.fn((asset) => {
return Promise.resolve<ArrayBuffer>(new Uint8Array(tfRecords).buffer);
});
HtmlFileReader.getAssetArray = jest.fn((asset) => {
return Promise.resolve<ArrayBuffer>(new Uint8Array(tfRecords).buffer);
});
const defaultProps: IAssetProps = {
asset: {
...MockFactory.createTestAsset("test"),
path: "abc",
},
onLoaded: onLoadHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onError: onErrorHandler,
};
defaultProps = {
asset: {
...MockFactory.createTestAsset("test"),
path: "abc",
},
onLoaded: onLoadHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onError: onErrorHandler,
};
});
function createComponent(props?: IAssetProps): ReactWrapper<IAssetProps, ITFRecordState> {
props = props || defaultProps;
@@ -98,7 +106,7 @@ describe("TFRecord Asset Component", () => {
});
it("raises onError handler when there is an error reading image data from tf record", async () => {
HtmlFileReader.getAssetArray = jest.fn(() => Promise.resolve());
HtmlFileReader.getAssetArray = jest.fn(() => Promise.resolve()) as any;
wrapper = createComponent();
await MockFactory.flushUi();
@@ -17,25 +17,32 @@ describe("Video Asset Component", () => {
seeking: false,
};
const videoPlayerMock = Player as jest.Mocked<typeof Player>;
const onLoadedHandler = jest.fn();
const onActivatedHandler = jest.fn();
const onDeactivatedHandler = jest.fn();
const onChildSelectedHandler = jest.fn();
const onBeforeAssetChangedHandler = jest.fn(() => true);
const defaultProps: IVideoAssetProps = {
asset: MockFactory.createVideoTestAsset("test-video"),
autoPlay: true,
controlsEnabled: true,
timestamp: 0,
onLoaded: onLoadedHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onChildAssetSelected: onChildSelectedHandler,
onBeforeAssetChanged: onBeforeAssetChangedHandler,
additionalSettings: { videoSettings: { frameExtractionRate: 1 } },
};
let onLoadedHandler = null;
let onActivatedHandler = null;
let onDeactivatedHandler = null;
let onChildSelectedHandler = null;
let onBeforeAssetChangedHandler = null;
let defaultProps: IVideoAssetProps = null;
beforeEach(() => {
onLoadedHandler = jest.fn();
onActivatedHandler = jest.fn();
onDeactivatedHandler = jest.fn();
onChildSelectedHandler = jest.fn();
onBeforeAssetChangedHandler = jest.fn(() => true);
defaultProps = {
asset: MockFactory.createVideoTestAsset("test-video"),
autoPlay: true,
controlsEnabled: true,
timestamp: 0,
onLoaded: onLoadedHandler,
onActivated: onActivatedHandler,
onDeactivated: onDeactivatedHandler,
onChildAssetSelected: onChildSelectedHandler,
onBeforeAssetChanged: onBeforeAssetChangedHandler,
additionalSettings: { videoSettings: { frameExtractionRate: 1 } },
};
videoPlayerMock.prototype.getState = jest.fn(() => ({ player: videoPlayerState }));
videoPlayerMock.prototype.subscribeToStateChange = jest.fn((handler) => onVideoStateChangeHandler = handler);
videoPlayerMock.prototype.pause = jest.fn();
@@ -8,20 +8,23 @@ import { IConnection } from "../../../../models/applicationState";
describe("CloudFilePicker", () => {
const mockFiles = MockFactory.createFileList();
const mockStorageProvider = MockFactory.createStorageProvider();
StorageProviderFactory.createFromConnection = jest.fn(
(connection: IConnection) => MockFactory.createStorageProviderFromConnection(connection));
let mockStorageProvider = null;
function createComponent(props: ICloudFilePickerProps):
ReactWrapper<ICloudFilePickerProps, ICloudFilePickerState, CloudFilePicker> {
return mount(<CloudFilePicker {...props}/>);
ReactWrapper<ICloudFilePickerProps, ICloudFilePickerState, CloudFilePicker> {
return mount(<CloudFilePicker {...props} />);
}
function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
beforeEach(() => {
mockStorageProvider = MockFactory.createStorageProvider();
StorageProviderFactory.createFromConnection = jest.fn(
(connection: IConnection) => MockFactory.createStorageProviderFromConnection(connection));
});
it("modal is visible", async () => {
const connections = MockFactory.createTestConnections();
const onCancel = jest.fn();
@@ -3,29 +3,35 @@ import { mount, ReactWrapper } from "enzyme";
import Confirm, { IConfirmProps, IConfirmState } from "./confirm";
describe("Confirm component", () => {
const modalConfirmHandler = jest.fn();
const modalCancelHandler = jest.fn();
const defaultProps: IConfirmProps = {
title: "Test Title",
message: "Test Message",
onConfirm: modalConfirmHandler,
onCancel: modalCancelHandler,
};
let modalConfirmHandler = null;
let modalCancelHandler = null;
let defaultProps: IConfirmProps = null;
function createComponent(props: IConfirmProps): ReactWrapper<IConfirmProps, IConfirmState, Confirm> {
return mount(<Confirm {...props}></Confirm>);
}
beforeEach(() => {
modalConfirmHandler = jest.fn();
modalCancelHandler = jest.fn();
defaultProps = {
title: "Test Title",
message: "Test Message",
onConfirm: modalConfirmHandler,
onCancel: modalCancelHandler,
};
});
it("Is defined", () => {
expect(Confirm).toBeDefined();
});
it("Renders nothing if not activiated", () => {
it("Renders nothing if not activated", () => {
const wrapper = createComponent(defaultProps);
expect(wrapper.html()).toBeNull();
expect(wrapper.html()).toEqual("");
});
it("Renders modal when activiated", () => {
it("Renders modal when activated", () => {
const wrapper = createComponent(defaultProps);
wrapper.instance().open();
@@ -58,7 +58,7 @@ export class ConnectionPicker extends React.Component<IConnectionPickerProps, IC
<option
className="connection-option"
key={connection.id}
value={connection.id}>{connection.name}
value={connection.id}>{this.getConnectionText(connection)}
</option>)
}
</select>
@@ -71,6 +71,18 @@ export class ConnectionPicker extends React.Component<IConnectionPickerProps, IC
);
}
private getConnectionText = (connection: IConnection): string => {
const options = connection.providerOptions;
if (options["folderPath"]) {
return `${connection.name} (${options["folderPath"]})`;
} else if (options["accountName"]) {
return `${connection.name} (Azure:${options["accountName"]}\\${options["containerName"]})`;
} else {
return connection.name;
}
}
private onChange = (e) => {
const selectedConnection = this.props.connections
.find((connection) => connection.id === e.target.value) || {};
@@ -10,29 +10,35 @@ jest.mock("../../../../providers/storage/assetProviderFactory");
import { AssetProviderFactory } from "../../../../providers/storage/assetProviderFactory";
describe("Connection Provider Picker", () => {
const storageProviderRegistrations = MockFactory.createStorageProviderRegistrations();
const assetProviderRegistrations = MockFactory.createAssetProviderRegistrations();
let storageProviderRegistrations = [];
let assetProviderRegistrations = [];
let wrapper: ReactWrapper;
const onChangeHandler = jest.fn();
const defaultProps: IConnectionProviderPickerProps = {
id: "test-connection-provider-picker",
value: "",
onChange: onChangeHandler,
};
let onChangeHandler = null;
let defaultProps: IConnectionProviderPickerProps = null;
function createComponent(props: IConnectionProviderPickerProps) {
return mount(<ConnectionProviderPicker {...props} />);
}
beforeAll(() => {
beforeEach(() => {
storageProviderRegistrations = MockFactory.createStorageProviderRegistrations();
assetProviderRegistrations = MockFactory.createAssetProviderRegistrations();
onChangeHandler = jest.fn();
defaultProps = {
id: "test-connection-provider-picker",
value: "",
onChange: onChangeHandler,
};
Object.defineProperty(StorageProviderFactory, "providers", {
get: jest.fn(() => storageProviderRegistrations),
configurable: true,
});
Object.defineProperty(AssetProviderFactory, "providers", {
get: jest.fn(() => assetProviderRegistrations),
configurable: true,
});
});
@@ -61,7 +67,7 @@ describe("Connection Provider Picker", () => {
expect(picker.find("option").length).toEqual(allProviders.length + 1);
});
it("Calls registred onChange handler when value changes", async () => {
it("Calls registered onChange handler when value changes", async () => {
await MockFactory.flushUi(() => {
wrapper.find("select").simulate("change", { target: { value: assetProviderRegistrations[1].name } });
});
@@ -1,75 +1,78 @@
import React from "react";
import { mount, ReactWrapper } from "enzyme";
import _ from "lodash";
import ExportProviderPicker, { IExportProviderPickerProps } from "./exportProviderPicker";
import MockFactory from "../../../../common/mockFactory";
jest.mock("../../../../providers/export/exportProviderFactory");
import { ExportProviderFactory } from "../../../../providers/export/exportProviderFactory";
describe("Export Provider Picker", () => {
const exportProviderRegistrations = MockFactory.createExportProviderRegistrations();
let wrapper: ReactWrapper;
const onChangeHandler = jest.fn();
const defaultProps: IExportProviderPickerProps = {
id: "test-export-provider-picker",
value: "azureCustomVision",
onChange: onChangeHandler,
};
function createComponent(props: IExportProviderPickerProps) {
return mount(<ExportProviderPicker {...props} />);
}
beforeAll(() => {
Object.defineProperty(ExportProviderFactory, "providers", {
get: jest.fn(() => exportProviderRegistrations),
});
});
describe("With default properties", () => {
beforeEach(() => {
wrapper = createComponent(defaultProps);
});
it("Renders a dropdown with all export providers", () => {
const exportProviders = _.values(exportProviderRegistrations);
const allProviders = _([])
.concat(exportProviders)
.orderBy("displayName")
.value();
const picker = wrapper.find("select");
const htmlNode = picker.getDOMNode() as HTMLSelectElement;
// Count of unique providers + the "Select" option
expect(htmlNode.id).toEqual(defaultProps.id);
expect(htmlNode.value).toEqual(defaultProps.value);
expect(picker.find("option").length).toEqual(allProviders.length);
});
it("Calls registred onChange handler when value changes", async () => {
await MockFactory.flushUi(() => {
wrapper.find("select").simulate("change", { target: { value: exportProviderRegistrations[1].name } });
});
expect(onChangeHandler).toBeCalledWith(exportProviderRegistrations[1].name);
});
});
describe("With property overrides", () => {
it("Selects correct option based on value", () => {
const props = {
...defaultProps,
value: exportProviderRegistrations[1].name,
};
wrapper = createComponent(props);
const htmlNode = wrapper.find("select").getDOMNode() as HTMLSelectElement;
expect(htmlNode.value).toEqual(props.value);
});
});
});
import React from "react";
import { mount, ReactWrapper } from "enzyme";
import _ from "lodash";
import ExportProviderPicker, { IExportProviderPickerProps } from "./exportProviderPicker";
import MockFactory from "../../../../common/mockFactory";
jest.mock("../../../../providers/export/exportProviderFactory");
import { ExportProviderFactory } from "../../../../providers/export/exportProviderFactory";
describe("Export Provider Picker", () => {
let exportProviderRegistrations = null;
let wrapper: ReactWrapper;
let onChangeHandler = null;
let defaultProps: IExportProviderPickerProps = null;
function createComponent(props: IExportProviderPickerProps) {
return mount(<ExportProviderPicker {...props} />);
}
beforeEach(() => {
exportProviderRegistrations = MockFactory.createExportProviderRegistrations();
onChangeHandler = jest.fn();
defaultProps = {
id: "test-export-provider-picker",
value: "azureCustomVision",
onChange: onChangeHandler,
};
Object.defineProperty(ExportProviderFactory, "providers", {
get: jest.fn(() => exportProviderRegistrations),
configurable: true
});
});
describe("With default properties", () => {
beforeEach(() => {
wrapper = createComponent(defaultProps);
});
it("Renders a dropdown with all export providers", () => {
const exportProviders = _.values(exportProviderRegistrations);
const allProviders = _([])
.concat(exportProviders)
.orderBy("displayName")
.value();
const picker = wrapper.find("select");
const htmlNode = picker.getDOMNode() as HTMLSelectElement;
// Count of unique providers + the "Select" option
expect(htmlNode.id).toEqual(defaultProps.id);
expect(htmlNode.value).toEqual(defaultProps.value);
expect(picker.find("option").length).toEqual(allProviders.length);
});
it("Calls registred onChange handler when value changes", async () => {
await MockFactory.flushUi(() => {
wrapper.find("select").simulate("change", { target: { value: exportProviderRegistrations[1].name } });
});
expect(onChangeHandler).toBeCalledWith(exportProviderRegistrations[1].name);
});
});
describe("With property overrides", () => {
it("Selects correct option based on value", () => {
const props = {
...defaultProps,
value: exportProviderRegistrations[1].name,
};
wrapper = createComponent(props);
const htmlNode = wrapper.find("select").getDOMNode() as HTMLSelectElement;
expect(htmlNode.value).toEqual(props.value);
});
});
});
@@ -5,29 +5,8 @@ import ExternalPicker, { IExternalPickerProps, IExternalPickerState, FilterOpera
import MockFactory from "../../../../common/mockFactory";
describe("External Picker", () => {
const onChangeHandler = jest.fn();
const defaultProps = createProps({
id: "my-custom-control",
value: "",
schema: {
title: "Item Name",
},
formContext: {
providerOptions: {
apiKey: "",
region: "",
},
},
onChange: onChangeHandler,
options: {
method: "GET",
url: "https://${props.formContext.providerOptions.region}.server.com/api",
keySelector: "${item.key}",
valueSelector: "${item.value}",
authHeaderName: "Authorization",
authHeaderValue: "${props.formContext.providerOptions.apiKey}",
},
});
let onChangeHandler = null;
let defaultProps = null;
const testResponse = [
{ key: "1", value: "Option 1" },
@@ -40,12 +19,36 @@ describe("External Picker", () => {
return mount(<ExternalPicker {...props} />);
}
beforeAll(() => {
beforeEach(() => {
onChangeHandler = jest.fn();
defaultProps = createProps({
id: "my-custom-control",
value: "",
schema: {
title: "Item Name",
},
formContext: {
providerOptions: {
apiKey: "",
region: "",
},
},
onChange: onChangeHandler,
options: {
method: "GET",
url: "https://${props.formContext.providerOptions.region}.server.com/api",
keySelector: "${item.key}",
valueSelector: "${item.value}",
authHeaderName: "Authorization",
authHeaderValue: "${props.formContext.providerOptions.apiKey}",
},
});
axios.request = jest.fn(() => {
return Promise.resolve({
data: testResponse,
});
});
}) as any;
});
it("Renders select element with default option", () => {
@@ -1,12 +1,13 @@
import React, { RefObject } from "react";
import React from "react";
import { ReactWrapper, mount } from "enzyme";
import FilePicker from "./filePicker";
import HtmlFileReader from "../../../../common/htmlFileReader";
import MockFactory from "../../../../common/mockFactory";
describe("File Picker Component", () => {
let wrapper: ReactWrapper = null;
const onChangeHandler = jest.fn();
const onErrorHandler = jest.fn();
let onChangeHandler = null;
let onErrorHandler = null;
function createComponent(): ReactWrapper {
return mount(
@@ -17,6 +18,8 @@ describe("File Picker Component", () => {
}
beforeEach(() => {
onChangeHandler = jest.fn();
onErrorHandler = jest.fn();
wrapper = createComponent();
});
@@ -26,9 +29,9 @@ describe("File Picker Component", () => {
expect(input.prop("type")).toEqual("file");
});
it("Calls the onChange handler on successfull file upload", (done) => {
it("Calls the onChange handler on successfully file upload", (done) => {
const expectedContent = "test file content";
HtmlFileReader.readAsText = jest.fn(() => Promise.resolve(expectedContent));
HtmlFileReader.readAsText = jest.fn(() => Promise.resolve(expectedContent)) as any;
const event: any = {
target: {
files: ["text.txt"],
@@ -43,7 +46,7 @@ describe("File Picker Component", () => {
});
});
it("Calls the onError handler on errored / cancelled file upload", (done) => {
it("Calls the onError handler on error / cancelled file upload", async () => {
const event: any = {
target: {
files: [],
@@ -52,9 +55,7 @@ describe("File Picker Component", () => {
wrapper.find("input").first().simulate("change", event);
setImmediate(() => {
expect(onErrorHandler).toBeCalledWith(expect.anything(), "No files were selected");
done();
});
await MockFactory.flushUi();
expect(onErrorHandler).toBeCalledWith(expect.anything(), "No files were selected");
});
});
@@ -42,7 +42,7 @@ export default class FilePicker extends React.Component<IFilePickerProps> {
private onFileUploaded = (e) => {
if (e.target.files.length === 0) {
this.props.onError(e, "No files were selected");
return this.props.onError(e, "No files were selected");
}
HtmlFileReader.readAsText(e.target.files[0])
@@ -8,19 +8,12 @@ import { KeyboardRegistrationManager } from "../keyboardManager/keyboardRegistra
describe("Keyboard Binding Component", () => {
let wrapper: ReactWrapper = null;
const onKeyDownHandler = jest.fn();
const deregisterFunc = jest.fn();
let onKeyDownHandler = null;
let deregisterFunc = null;
let defaultProps: IKeyboardBindingProps = null
const accelerators = ["CmdOrCtrl+1"];
const defaultProps: IKeyboardBindingProps = {
displayName: "Keyboard binding",
keyEventType: KeyEventType.KeyDown,
accelerators,
handler: onKeyDownHandler,
};
const registrationMock = KeyboardRegistrationManager as jest.Mocked<typeof KeyboardRegistrationManager>;
registrationMock.prototype.registerBinding = jest.fn(() => deregisterFunc);
let registrationMock = null;
function createComponent(props?: IKeyboardBindingProps): ReactWrapper {
props = props || defaultProps;
@@ -32,6 +25,21 @@ describe("Keyboard Binding Component", () => {
);
}
beforeEach(() => {
onKeyDownHandler = jest.fn();
deregisterFunc = jest.fn();
defaultProps = {
displayName: "Keyboard binding",
keyEventType: KeyEventType.KeyDown,
accelerators,
handler: onKeyDownHandler,
};
registrationMock = KeyboardRegistrationManager as jest.Mocked<typeof KeyboardRegistrationManager>;
registrationMock.prototype.registerBinding = jest.fn(() => deregisterFunc);
});
it("is defined", () => {
wrapper = createComponent();
expect(wrapper).not.toBeNull();
@@ -72,16 +72,16 @@ describe("Active Learning Form", () => {
};
// Set type to URL
wrapper.find(Form).props().onChange({ formData: { modelPathType: ModelPathType.Url } });
wrapper.find(Form).props().onChange({ formData: { modelPathType: ModelPathType.Url } } as any);
// Set the remaining settings
wrapper.find(Form).props().onChange({ formData });
wrapper.find(Form).props().onChange({ formData } as any);
expect(wrapper.state().formData).toEqual(formData);
expect(onChangeHandler).toBeCalledWith(formData);
});
it("submits form data to the registered submit handler", () => {
const wrapper = createComponent();
wrapper.find(Form).props().onSubmit({ formData: defaultProps.settings });
wrapper.find(Form).props().onSubmit({ formData: defaultProps.settings } as any);
expect(onSubmitHandler).toBeCalledWith(defaultProps.settings);
});
@@ -66,7 +66,7 @@ describe("Editor Canvas", () => {
const editorMock = Editor as any;
beforeAll(() => {
beforeEach(() => {
let selectionMode = {
mode: SelectionMode.NONE,
template: null,
@@ -75,7 +75,7 @@ describe("Editor Page Component", () => {
beforeAll(() => {
registerToolbar();
window["require"] = jest.fn(() => electronMock);
window["require"] = jest.fn(() => electronMock) as any;
const editorMock = Editor as any;
editorMock.prototype.addContentSource = jest.fn(() => Promise.resolve());
@@ -11,6 +11,7 @@ import { IVottJsonExportProviderOptions } from "../../../../providers/export/vot
describe("Export Form Component", () => {
const exportProviderRegistrations = MockFactory.createExportProviderRegistrations();
let onSubmitHandler = null;
function createComponent(props: IExportFormProps) {
return mount(
@@ -18,17 +19,19 @@ describe("Export Form Component", () => {
);
}
beforeAll(() => {
beforeEach(() => {
onSubmitHandler = jest.fn();
Object.defineProperty(ExportProviderFactory, "providers", {
get: jest.fn(() => exportProviderRegistrations),
configurable: true,
});
Object.defineProperty(ExportProviderFactory, "defaultProvider", {
get: jest.fn(() => exportProviderRegistrations[0]),
configurable: true,
});
});
const onSubmitHandler = jest.fn();
it("State is initialized without export settings", () => {
const defaultExportType = "vottJson";
const props: IExportFormProps = {
@@ -84,7 +84,7 @@ describe("Export Page", () => {
return {
export: jest.fn(() => Promise.resolve()),
};
});
}) as any;
projectServiceMock.prototype.save = jest.fn((project) => Promise.resolve(project));
@@ -36,7 +36,7 @@ describe("Homepage Component", () => {
writeText: jest.fn((project) => Promise.resolve(project)),
deleteFile: jest.fn(() => Promise.resolve()),
};
StorageProviderFactory.create = jest.fn(() => storageProviderMock);
StorageProviderFactory.create = jest.fn(() => storageProviderMock) as any;
function createComponent(store, props: IHomePageProps): ReactWrapper {
return mount(
@@ -53,7 +53,7 @@ describe("Project settings page", () => {
localStorageMock.removeItem.mockClear();
projectServiceMock = ProjectService as jest.Mocked<typeof ProjectService>;
projectServiceMock.prototype.load = jest.fn((project) => ({ ...project }));
projectServiceMock.prototype.load = jest.fn((project) => ({ ...project })) as any;
});
it("Form submission calls save project action", async () => {
+13 -7
Ver Arquivo
@@ -2,8 +2,10 @@ import { mount } from "enzyme";
import React from "react";
import MockFactory from "../../../common/mockFactory";
import { KeyboardManager } from "../common/keyboardManager/keyboardManager";
import { IKeyboardRegistrations,
KeyboardRegistrationManager } from "../common/keyboardManager/keyboardRegistrationManager";
import {
IKeyboardRegistrations,
KeyboardRegistrationManager
} from "../common/keyboardManager/keyboardRegistrationManager";
import { HelpMenu, IHelpMenuProps } from "./helpMenu";
jest.mock("../common/keyboardManager/keyboardRegistrationManager");
@@ -11,16 +13,20 @@ describe("Help Menu", () => {
function createComponent(props?: IHelpMenuProps) {
return mount(
<KeyboardManager>
<HelpMenu {...props}/>
<HelpMenu {...props} />
</KeyboardManager>,
);
}
const numberRegistrations = 5;
const keyboardRegistrations: IKeyboardRegistrations = MockFactory.createKeyboardRegistrations(numberRegistrations);
const registrationMock = KeyboardRegistrationManager as jest.Mocked<typeof KeyboardRegistrationManager>;
let registrationMock = null;
registrationMock.prototype.getRegistrations = jest.fn(() => keyboardRegistrations);
registrationMock.prototype.registerBinding = jest.fn(() => jest.fn());
beforeEach(() => {
registrationMock = KeyboardRegistrationManager as jest.Mocked<typeof KeyboardRegistrationManager>;
registrationMock.prototype.getRegistrations = jest.fn(() => keyboardRegistrations);
registrationMock.prototype.registerBinding = jest.fn(() => jest.fn());
});
it("Opens when button is clicked", () => {
const wrapper = createComponent();
@@ -51,7 +57,7 @@ describe("Help Menu", () => {
it("Calls onClose handler when closed", () => {
const onClose = jest.fn();
const wrapper = createComponent({onClose});
const wrapper = createComponent({ onClose });
wrapper.find("div.help-menu-button").simulate("click");
expect(wrapper.exists("div.modal-content")).toBe(true);
wrapper.find("button.close").simulate("click");

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais