mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-24 03:11:32 +00:00
Compare commits
1 Commits
fix-emulat
...
e2e-produc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f7e0d4d1 |
@@ -343,7 +343,7 @@ src/Explorer/Controls/LibraryManagement/LibraryManageComponentAdapter.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
|
||||
src/NotebookViewer/NotebookViewer.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewer.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
|
||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponentAdapter.tsx
|
||||
|
||||
26
.github/workflows/blank.yml
vendored
26
.github/workflows/blank.yml
vendored
@@ -67,6 +67,32 @@ jobs:
|
||||
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||
endtoendprodcassandra:
|
||||
name: "End to End Tests Prod"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Restore Cypress Binary Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-binary-cache
|
||||
- name: End to End Tests
|
||||
run: |
|
||||
npm ci
|
||||
npm start &
|
||||
npm ci --prefix ./cypress
|
||||
npm run test --prefix ./cypress
|
||||
shell: bash
|
||||
env:
|
||||
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||
CONNECTION_STRING: ${{ secrets.CASSANDRA_CONNECTION_STRING }}
|
||||
nuget:
|
||||
name: Publish Nuget
|
||||
needs: [build, test, endtoend]
|
||||
|
||||
324
package-lock.json
generated
324
package-lock.json
generated
@@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@azure/cosmos": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.0.tgz",
|
||||
"integrity": "sha512-3SRxnmy6NncdX5eYqGuRTack52hloS9YhQ0aOKwWJ8Z4dDSrVH3XB2Mcp/WokoIpVm0Bq5nUC8FsvLBZKfRkyg==",
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.6.3.tgz",
|
||||
"integrity": "sha512-JoCDxl0TnL6EHL4xD3KC9r2bMivK13q1jl7h69wd/YFLlt3aBTTCehtAX+y4alNSENpL53XdRdw/cna0mI2XDw==",
|
||||
"requires": {
|
||||
"@types/debug": "^4.1.4",
|
||||
"debug": "^4.1.1",
|
||||
@@ -18,14 +18,7 @@
|
||||
"priorityqueuejs": "^1.0.0",
|
||||
"semaphore": "^1.0.5",
|
||||
"tslib": "^1.10.0",
|
||||
"uuid": "^8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||
}
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"@azure/cosmos-language-service": {
|
||||
@@ -1166,6 +1159,11 @@
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@emotion/hash": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
|
||||
},
|
||||
"@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
@@ -1606,6 +1604,96 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@material-ui/core": {
|
||||
"version": "4.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.10.tgz",
|
||||
"integrity": "sha512-CQuZU9Y10RkwSdxjn785kw2EPcXhv5GKauuVQufR9LlD37kjfn21Im1yvr6wsUzn81oLhEvVPz727UWC0gbqxg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@material-ui/styles": "^4.9.10",
|
||||
"@material-ui/system": "^4.9.10",
|
||||
"@material-ui/types": "^5.0.1",
|
||||
"@material-ui/utils": "^4.9.6",
|
||||
"@types/react-transition-group": "^4.2.0",
|
||||
"clsx": "^1.0.4",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"popper.js": "^1.16.1-lts",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.0",
|
||||
"react-transition-group": "^4.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dom-helpers": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
|
||||
"integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@material-ui/styles": {
|
||||
"version": "4.9.14",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.14.tgz",
|
||||
"integrity": "sha512-zecwWKgRU2VzdmutNovPB4s5LKI0TWyZKc/AHfPu9iY8tg4UoLjpa4Rn9roYrRfuTbBZHI6b0BXcQ8zkis0nzQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@emotion/hash": "^0.8.0",
|
||||
"@material-ui/types": "^5.1.0",
|
||||
"@material-ui/utils": "^4.9.6",
|
||||
"clsx": "^1.0.4",
|
||||
"csstype": "^2.5.2",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"jss": "^10.0.3",
|
||||
"jss-plugin-camel-case": "^10.0.3",
|
||||
"jss-plugin-default-unit": "^10.0.3",
|
||||
"jss-plugin-global": "^10.0.3",
|
||||
"jss-plugin-nested": "^10.0.3",
|
||||
"jss-plugin-props-sort": "^10.0.3",
|
||||
"jss-plugin-rule-value-function": "^10.0.3",
|
||||
"jss-plugin-vendor-prefixer": "^10.0.3",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"@material-ui/system": {
|
||||
"version": "4.9.14",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz",
|
||||
"integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@material-ui/utils": "^4.9.6",
|
||||
"csstype": "^2.5.2",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"@material-ui/types": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
|
||||
"integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
|
||||
},
|
||||
"@material-ui/utils": {
|
||||
"version": "4.9.12",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.12.tgz",
|
||||
"integrity": "sha512-/0rgZPEOcZq5CFA4+4n6Q6zk7fi8skHhH2Bcra8R3epoJEYy5PL55LuMazPtPH1oKeRausDV/Omz4BbgFsn1HQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.0"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-analytics-js": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.5.4.tgz",
|
||||
@@ -2429,42 +2517,32 @@
|
||||
}
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.1.tgz",
|
||||
"integrity": "sha512-NB81O5h39KfHYGtgfWr2booRxp2bWOJoqbWwbyUg2hw6h35ArWYlAST5B3XwAkbdcx13yt84hFXyFP5X0QToWA==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz",
|
||||
"integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/types": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.2.tgz",
|
||||
"integrity": "sha512-+4X6qfhT/fk/5FD66395NrFLxCzD6FsGlpPwfwvnukdyfYbhiZB/FJltiT1XM5Q63rGGBSf9FPaNV3WpNHm54A==",
|
||||
"requires": {
|
||||
"@types/node": ">= 8"
|
||||
}
|
||||
}
|
||||
"@octokit/types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.3.tgz",
|
||||
"integrity": "sha512-23AHK9xBW0v79Ck8h5U+5iA4MW7aosqv+Yr6uZXolVGNzzHwryNH5wM386/6+etiKUTwLFZTqyMU9oQpIBZcFA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.0.tgz",
|
||||
"integrity": "sha512-uvzmkemQrBgD8xuGbjhxzJN1darJk9L2cS+M99cHrDG2jlSVpxNJVhoV86cXdYBqdHCc9Z995uLCczaaHIYA6Q==",
|
||||
"requires": {
|
||||
"@octokit/auth-token": "^2.4.0",
|
||||
"@octokit/graphql": "^4.3.1",
|
||||
"@octokit/request": "^5.4.0",
|
||||
"@octokit/types": "^4.0.1",
|
||||
"@octokit/types": "^2.0.0",
|
||||
"before-after-hook": "^2.1.0",
|
||||
"universal-user-agent": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/endpoint": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.2.tgz",
|
||||
"integrity": "sha512-xs1mmCEZ2y4shXCpFjNq3UbmNR+bLzxtZim2L0zfEtj9R6O6kc4qLDvYw66hvO6lUsYzPTM5hMkltbuNAbRAcQ==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.1.tgz",
|
||||
"integrity": "sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A==",
|
||||
"requires": {
|
||||
"@octokit/types": "^4.0.1",
|
||||
"@octokit/types": "^2.11.1",
|
||||
"is-plain-object": "^3.0.0",
|
||||
"universal-user-agent": "^5.0.0"
|
||||
},
|
||||
@@ -2485,21 +2563,21 @@
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.0.tgz",
|
||||
"integrity": "sha512-StJWfn0M1QfhL3NKBz31e1TdDNZrHLLS57J2hin92SIfzlOVBuUaRkp31AGkGOAFOAVtyEX6ZiZcsjcJDjeb5g==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.4.0.tgz",
|
||||
"integrity": "sha512-Du3hAaSROQ8EatmYoSAJjzAz3t79t9Opj/WY1zUgxVUGfIKn0AEjg+hlOLscF6fv6i/4y/CeUvsWgIfwMkTccw==",
|
||||
"requires": {
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^4.0.1",
|
||||
"@octokit/types": "^2.0.0",
|
||||
"universal-user-agent": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-paginate-rest": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.1.tgz",
|
||||
"integrity": "sha512-/tHpIF2XpN40AyhIq295YRjb4g7Q5eKob0qM3thYJ0Z+CgmNsWKM/fWse/SUR8+LdprP1O4ZzSKQE+71TCwK+w==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.0.tgz",
|
||||
"integrity": "sha512-KoNxC3PLNar8UJwR+1VMQOw2IoOrrFdo5YOiDKnBhpVbKpw+zkBKNMNKwM44UWL25Vkn0Sl3nYIEGKY+gW5ebw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^4.0.1"
|
||||
"@octokit/types": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-request-log": {
|
||||
@@ -2508,22 +2586,22 @@
|
||||
"integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw=="
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "3.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.12.3.tgz",
|
||||
"integrity": "sha512-9nrVDP1tBd7EtobGr5hZcYGTM0kBNmIvPJazrUd5OJO0NZWiQaQOqAnzApmC9cZ4o7RempV21ScpWkKGhrT51A==",
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.7.1.tgz",
|
||||
"integrity": "sha512-YOlcE3bbk2ohaOVdRj9ww7AUYfmnS9hwJJGSj3/rFlNfMGOId4G8dLlhghXpdNSn05H0FRoI94UlFUKnn30Cyw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^4.0.0",
|
||||
"@octokit/types": "^2.11.1",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.4.tgz",
|
||||
"integrity": "sha512-vqv1lz41c6VTxUvF9nM+a6U+vvP3vGk7drDpr0DVQg4zyqlOiKVrY17DLD6de5okj+YLHKcoqaUZTBtlNZ1BtQ==",
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.2.tgz",
|
||||
"integrity": "sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^4.0.1",
|
||||
"@octokit/types": "^2.11.1",
|
||||
"deprecation": "^2.0.0",
|
||||
"is-plain-object": "^3.0.0",
|
||||
"node-fetch": "^2.3.0",
|
||||
@@ -2547,32 +2625,39 @@
|
||||
}
|
||||
},
|
||||
"@octokit/request-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.1.tgz",
|
||||
"integrity": "sha512-5lqBDJ9/TOehK82VvomQ6zFiZjPeSom8fLkFVLuYL3sKiIb5RB8iN/lenLkY7oBmyQcGP7FBMGiIZTO8jufaRQ==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz",
|
||||
"integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^4.0.1",
|
||||
"@octokit/types": "^2.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"@octokit/rest": {
|
||||
"version": "17.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.9.2.tgz",
|
||||
"integrity": "sha512-UXxiE0HhGQAPB3WDHTEu7lYMHH2uRcs/9f26XyHpGGiiXht8hgHWEk6fA7WglwwEvnj8V7mkJOgIntnij132UA==",
|
||||
"version": "17.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.5.1.tgz",
|
||||
"integrity": "sha512-0rGY7eo0cw8FYX7jAtUgfy3j+05zhs9JvkPFegx00HAaayodM1ixlHhCOB5yirGbsVOxbRIWVkvKc2yY9367gg==",
|
||||
"requires": {
|
||||
"@octokit/core": "^2.4.3",
|
||||
"@octokit/plugin-paginate-rest": "^2.2.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.1.0",
|
||||
"@octokit/plugin-request-log": "^1.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^3.12.2"
|
||||
"@octokit/plugin-rest-endpoint-methods": "3.7.1"
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.2.tgz",
|
||||
"integrity": "sha512-+4X6qfhT/fk/5FD66395NrFLxCzD6FsGlpPwfwvnukdyfYbhiZB/FJltiT1XM5Q63rGGBSf9FPaNV3WpNHm54A==",
|
||||
"version": "2.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz",
|
||||
"integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==",
|
||||
"requires": {
|
||||
"@types/node": ">= 8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.1.tgz",
|
||||
"integrity": "sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@peculiar/asn1-schema": {
|
||||
@@ -3205,7 +3290,8 @@
|
||||
"@types/node": {
|
||||
"version": "12.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
|
||||
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
|
||||
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/promise.prototype.finally": {
|
||||
"version": "2.0.3",
|
||||
@@ -3285,6 +3371,14 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz",
|
||||
"integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/shallowequal": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/shallowequal/-/shallowequal-1.1.1.tgz",
|
||||
@@ -5530,6 +5624,11 @@
|
||||
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
|
||||
"dev": true
|
||||
},
|
||||
"clsx": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz",
|
||||
"integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA=="
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
@@ -6094,6 +6193,15 @@
|
||||
"postcss-value-parser": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"css-vendor": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
|
||||
"integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.3",
|
||||
"is-in-browser": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||
@@ -9807,6 +9915,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hyphenate-style-name": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
|
||||
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -10346,6 +10459,11 @@
|
||||
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
|
||||
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
|
||||
},
|
||||
"is-in-browser": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
|
||||
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
|
||||
},
|
||||
"is-number": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
@@ -11215,6 +11333,83 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"jss": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
|
||||
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"csstype": "^2.6.5",
|
||||
"is-in-browser": "^1.1.3",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-plugin-camel-case": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz",
|
||||
"integrity": "sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"hyphenate-style-name": "^1.0.3",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jss-plugin-default-unit": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz",
|
||||
"integrity": "sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jss-plugin-global": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz",
|
||||
"integrity": "sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jss-plugin-nested": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz",
|
||||
"integrity": "sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.1.1",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-plugin-props-sort": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz",
|
||||
"integrity": "sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jss-plugin-rule-value-function": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz",
|
||||
"integrity": "sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jss-plugin-vendor-prefixer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz",
|
||||
"integrity": "sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"css-vendor": "^2.0.7",
|
||||
"jss": "10.1.1"
|
||||
}
|
||||
},
|
||||
"jsx-ast-utils": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
|
||||
@@ -16600,6 +16795,11 @@
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||
"optional": true
|
||||
},
|
||||
"tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||
},
|
||||
"tinycolor2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
"description": "Cosmos Explorer",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "3.7.0",
|
||||
"@azure/cosmos": "3.6.3",
|
||||
"@azure/cosmos-language-service": "0.0.4",
|
||||
"@jupyterlab/services": "4.2.0",
|
||||
"@jupyterlab/terminal": "1.2.1",
|
||||
"@material-ui/core": "4.9.10",
|
||||
"@microsoft/applicationinsights-web": "2.5.4",
|
||||
"@nteract/commutable": "7.1.4",
|
||||
"@nteract/connected-components": "6.7.8",
|
||||
@@ -32,7 +33,7 @@
|
||||
"@nteract/transform-plotly": "6.1.6",
|
||||
"@nteract/transform-vdom": "4.0.11",
|
||||
"@nteract/transform-vega": "7.0.6",
|
||||
"@octokit/rest": "17.9.2",
|
||||
"@octokit/rest": "17.5.1",
|
||||
"@phosphor/widgets": "1.9.3",
|
||||
"@uifabric/react-cards": "0.109.53",
|
||||
"@uifabric/styling": "7.11.2",
|
||||
|
||||
@@ -21,15 +21,13 @@ const _global = typeof self === "undefined" ? window : self;
|
||||
export const tokenProvider = async (requestInfo: RequestInfo) => {
|
||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||
if (config.platform === Platform.Emulator) {
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
|
||||
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey) as any;
|
||||
}
|
||||
|
||||
if (_masterKey) {
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
|
||||
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, _masterKey) as any;
|
||||
}
|
||||
|
||||
if (_resourceToken) {
|
||||
@@ -49,9 +47,7 @@ export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) =>
|
||||
|
||||
export const endpoint = () => {
|
||||
if (config.platform === Platform.Emulator) {
|
||||
// In worker scope, _global(self).parent does not exist
|
||||
const location = _global.parent ? _global.parent.location : _global.location;
|
||||
return config.EMULATOR_ENDPOINT || location.origin;
|
||||
return config.EMULATOR_ENDPOINT || window.parent.location.origin;
|
||||
}
|
||||
return _endpoint || (_databaseAccount && _databaseAccount.properties && _databaseAccount.properties.documentEndpoint);
|
||||
};
|
||||
|
||||
@@ -738,8 +738,6 @@ export interface GitHubInfoJunoResponse {
|
||||
gitUrl: string;
|
||||
htmlUrl: string;
|
||||
metadata?: NotebookMetadata;
|
||||
officialSamplesIndex?: number;
|
||||
isLikedNotebook?: boolean;
|
||||
}
|
||||
|
||||
export interface LikedNotebooksJunoResponse {
|
||||
|
||||
@@ -229,12 +229,7 @@ export interface Explorer {
|
||||
importAndOpenFromGallery: (path: string, newName: string, content: any) => Promise<boolean>;
|
||||
openNotebookTerminal: (kind: TerminalKind) => void;
|
||||
openGallery: () => void;
|
||||
openNotebookViewer: (
|
||||
notebookUrl: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => void;
|
||||
openNotebookViewer: (notebookUrl: string, notebookMetadata: DataModels.NotebookMetadata) => void;
|
||||
notebookWorkspaceManager: NotebookWorkspaceManager;
|
||||
sparkClusterManager: SparkClusterManager;
|
||||
notebookContentProvider: IContentProvider;
|
||||
@@ -892,8 +887,6 @@ export interface NotebookViewerTabOptions extends TabOptions {
|
||||
notebookUrl: string;
|
||||
notebookName: string;
|
||||
notebookMetadata: DataModels.NotebookMetadata;
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>;
|
||||
isLikedNotebook: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentsTabOptions extends TabOptions {
|
||||
|
||||
@@ -54,8 +54,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
||||
styles: {
|
||||
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
|
||||
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
|
||||
},
|
||||
showCloseButton: false
|
||||
}
|
||||
},
|
||||
modalProps: { isBlocking: this.props.isModal },
|
||||
minWidth: DIALOG_MIN_WIDTH,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* React component for Switch Directory
|
||||
*/
|
||||
|
||||
import _ from "underscore";
|
||||
import * as React from "react";
|
||||
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { Tenant } from "../../../Contracts/DataModels";
|
||||
@@ -61,7 +60,7 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === option.key);
|
||||
const selectedDirectory = this.props.directories.find(d => d.tenantId === option.key);
|
||||
if (!selectedDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "underscore";
|
||||
import * as React from "react";
|
||||
|
||||
import { DefaultButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||
@@ -115,7 +114,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
|
||||
}
|
||||
const buttonElement = e.currentTarget;
|
||||
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
|
||||
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === selectedDirectoryId);
|
||||
const selectedDirectory = this.props.directories.find(d => d.tenantId === selectedDirectoryId);
|
||||
|
||||
this.props.onNewDirectorySelected(selectedDirectory);
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo);
|
||||
if (repo) {
|
||||
const item: RepoListItem = {
|
||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||
key: GitHubUtils.toRepoFullName(repo.owner.login, repo.name),
|
||||
repo,
|
||||
branches: [
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Text
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
|
||||
import { IGitHubBranch } from "../../../GitHub/GitHubClient";
|
||||
import { GitHubUtils } from "../../../Utils/GitHubUtils";
|
||||
import { RepoListItem } from "./GitHubReposComponent";
|
||||
import {
|
||||
@@ -41,7 +41,6 @@ export interface ReposListComponentProps {
|
||||
|
||||
export interface BranchesProps {
|
||||
branches: IGitHubBranch[];
|
||||
lastPageInfo?: IGitHubPageInfo;
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
loadMore: () => void;
|
||||
@@ -140,7 +139,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
}
|
||||
|
||||
const checkboxProps: ICheckboxProps = {
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner.login, item.repo.name)),
|
||||
styles: ReposListCheckboxStyles,
|
||||
defaultChecked: true,
|
||||
onChange: () => this.props.unpinRepo(item)
|
||||
@@ -154,7 +153,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner.login, item.repo.name)];
|
||||
const options: IDropdownOption[] = branchesProps.branches.map(branch => ({
|
||||
key: branch.name,
|
||||
text: branch.name,
|
||||
@@ -223,7 +222,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
|
||||
private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element {
|
||||
const item: RepoListItem = option.data;
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner.login, item.repo.name)];
|
||||
|
||||
if (option.index === ReposListComponent.FooterIndex) {
|
||||
const linkProps: ILinkProps = {
|
||||
@@ -268,7 +267,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
}
|
||||
|
||||
const checkboxProps: ICheckboxProps = {
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner.login, item.repo.name)),
|
||||
styles: ReposListCheckboxStyles,
|
||||
onChange: () => {
|
||||
const repoListItem = { ...item };
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./GalleryCardComponent";
|
||||
|
||||
describe("GalleryCardComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: GalleryCardComponentProps = {
|
||||
name: "mycard",
|
||||
url: "url",
|
||||
notebookMetadata: undefined,
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onClick: () => {}
|
||||
};
|
||||
|
||||
const wrapper = shallow(<GalleryCardComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GalleryCardComponent renders 1`] = `
|
||||
<Card
|
||||
aria-label="Notebook Card"
|
||||
onClick={[Function]}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenMargin": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CardSection>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "#333333",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
mycard
|
||||
</Text>
|
||||
</CardSection>
|
||||
</Card>
|
||||
`;
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.galleryContainer {
|
||||
padding: @LargeSpace @LargeSpace 30px @LargeSpace;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
font-family: @DataExplorerFont;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import {
|
||||
GalleryViewerContainerComponent,
|
||||
GalleryViewerContainerComponentProps,
|
||||
FullWidthTabs,
|
||||
FullWidthTabsProps,
|
||||
GalleryCardsComponent,
|
||||
GalleryCardsComponentProps,
|
||||
GalleryViewerComponent,
|
||||
GalleryViewerComponentProps
|
||||
} from "./GalleryViewerComponent";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
|
||||
describe("GalleryCardsComponent", () => {
|
||||
it("renders", () => {
|
||||
// TODO Mock this
|
||||
const props: GalleryCardsComponentProps = {
|
||||
data: [],
|
||||
userMetadata: undefined,
|
||||
onNotebookMetadataChange: (officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata) =>
|
||||
Promise.resolve(),
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise.resolve()
|
||||
};
|
||||
|
||||
const wrapper = shallow(<GalleryCardsComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("FullWidthTabs", () => {
|
||||
it("renders", () => {
|
||||
const props: FullWidthTabsProps = {
|
||||
officialSamplesContent: [],
|
||||
likedNotebooksContent: [],
|
||||
userMetadata: undefined,
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise.resolve()
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FullWidthTabs {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GalleryViewerContainerComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: GalleryViewerContainerComponentProps = {
|
||||
container: undefined
|
||||
};
|
||||
|
||||
const wrapper = shallow(<GalleryViewerContainerComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GalleryCardComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: GalleryViewerComponentProps = {
|
||||
container: undefined,
|
||||
officialSamplesData: [],
|
||||
likedNotebookData: undefined
|
||||
};
|
||||
|
||||
const wrapper = shallow(<GalleryViewerComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,356 +0,0 @@
|
||||
/**
|
||||
* Gallery Viewer
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { GalleryCardComponent } from "./Cards/GalleryCardComponent";
|
||||
import { Stack, IStackTokens } from "office-ui-fabric-react";
|
||||
import { JunoUtils } from "../../../Utils/JunoUtils";
|
||||
import { CosmosClient } from "../../../Common/CosmosClient";
|
||||
import { config } from "../../../Config";
|
||||
import path from "path";
|
||||
import { SessionStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||
import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as TabComponent from "../Tabs/TabComponent";
|
||||
|
||||
import "./GalleryViewerComponent.less";
|
||||
|
||||
export interface GalleryCardsComponentProps {
|
||||
data: DataModels.GitHubInfoJunoResponse[];
|
||||
userMetadata: DataModels.UserMetadata;
|
||||
onNotebookMetadataChange: (
|
||||
officialSamplesIndex: number,
|
||||
notebookMetadata: DataModels.NotebookMetadata
|
||||
) => Promise<void>;
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export class GalleryCardsComponent extends React.Component<GalleryCardsComponentProps> {
|
||||
private sectionStackTokens: IStackTokens = { childrenGap: 30 };
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack horizontal wrap tokens={this.sectionStackTokens}>
|
||||
{this.props.data.map((githubInfo: DataModels.GitHubInfoJunoResponse, index: any) => {
|
||||
const name = githubInfo.name;
|
||||
const url = githubInfo.downloadUrl;
|
||||
const notebookMetadata = githubInfo.metadata || {
|
||||
date: "2008-12-01",
|
||||
description: "Great notebook",
|
||||
tags: ["favorite", "sample"],
|
||||
author: "Laurent Nguyen",
|
||||
views: 432,
|
||||
likes: 123,
|
||||
downloads: 56,
|
||||
imageUrl:
|
||||
"https://media.magazine.ferrari.com/images/2019/02/27/170304506-c1bcf028-b513-45f6-9f27-0cadac619c3d.jpg"
|
||||
};
|
||||
const officialSamplesIndex = githubInfo.officialSamplesIndex;
|
||||
const isLikedNotebook = githubInfo.isLikedNotebook;
|
||||
const updateTabsStatePerNotebook = this.props.onNotebookMetadataChange
|
||||
? (notebookMetadata: DataModels.NotebookMetadata) =>
|
||||
this.props.onNotebookMetadataChange(officialSamplesIndex, notebookMetadata)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
name !== ".gitignore" &&
|
||||
url && (
|
||||
<GalleryCardComponent
|
||||
key={url}
|
||||
name={name}
|
||||
url={url}
|
||||
notebookMetadata={notebookMetadata}
|
||||
onClick={() => this.props.onClick(url, notebookMetadata, updateTabsStatePerNotebook, isLikedNotebook)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface FullWidthTabsProps {
|
||||
officialSamplesContent: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebooksContent: DataModels.GitHubInfoJunoResponse[];
|
||||
userMetadata: DataModels.UserMetadata;
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
interface FullWidthTabsState {
|
||||
activeTabIndex: number;
|
||||
officialSamplesContent: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebooksContent: DataModels.GitHubInfoJunoResponse[];
|
||||
userMetadata: DataModels.UserMetadata;
|
||||
}
|
||||
|
||||
export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidthTabsState> {
|
||||
private authorizationToken = CosmosClient.authorizationToken();
|
||||
private appTabs: TabComponent.Tab[];
|
||||
|
||||
constructor(props: FullWidthTabsProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTabIndex: 0,
|
||||
officialSamplesContent: this.props.officialSamplesContent,
|
||||
likedNotebooksContent: this.props.likedNotebooksContent,
|
||||
userMetadata: this.props.userMetadata
|
||||
};
|
||||
|
||||
this.appTabs = [
|
||||
{
|
||||
title: "Official Samples",
|
||||
content: {
|
||||
className: "",
|
||||
render: () => (
|
||||
<GalleryCardsComponent
|
||||
data={this.state.officialSamplesContent}
|
||||
onClick={this.props.onClick}
|
||||
userMetadata={this.state.userMetadata}
|
||||
onNotebookMetadataChange={this.updateTabsState}
|
||||
/>
|
||||
)
|
||||
},
|
||||
isVisible: () => true
|
||||
},
|
||||
{
|
||||
title: "Liked Notebooks",
|
||||
content: {
|
||||
className: "",
|
||||
render: () => (
|
||||
<GalleryCardsComponent
|
||||
data={this.state.likedNotebooksContent}
|
||||
onClick={this.props.onClick}
|
||||
userMetadata={this.state.userMetadata}
|
||||
onNotebookMetadataChange={this.updateTabsState}
|
||||
/>
|
||||
)
|
||||
},
|
||||
isVisible: () => true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public updateTabsState = async (officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata) => {
|
||||
let currentLikedNotebooksContent = [...this.state.likedNotebooksContent];
|
||||
let currentUserMetadata = { ...this.state.userMetadata };
|
||||
let currentLikedNotebooks = [...currentUserMetadata.likedNotebooks];
|
||||
|
||||
const currentOfficialSamplesContent = [...this.state.officialSamplesContent];
|
||||
const currentOfficialSamplesObject = { ...currentOfficialSamplesContent[officialSamplesIndex] };
|
||||
const metadata = { ...currentOfficialSamplesObject.metadata };
|
||||
const metadataLikesUpdates = metadata.likes - notebookMetadata.likes;
|
||||
|
||||
metadata.views = notebookMetadata.views;
|
||||
metadata.downloads = notebookMetadata.downloads;
|
||||
metadata.likes = notebookMetadata.likes;
|
||||
currentOfficialSamplesObject.metadata = metadata;
|
||||
|
||||
// Notebook has been liked. Add To likedNotebooksContent, update isLikedNotebook flag
|
||||
if (metadataLikesUpdates < 0) {
|
||||
currentOfficialSamplesObject.isLikedNotebook = true;
|
||||
currentLikedNotebooksContent = currentLikedNotebooksContent.concat(currentOfficialSamplesObject);
|
||||
currentLikedNotebooks = currentLikedNotebooks.concat(currentOfficialSamplesObject.path);
|
||||
currentUserMetadata = { likedNotebooks: currentLikedNotebooks };
|
||||
} else if (metadataLikesUpdates > 0) {
|
||||
// Notebook has been unliked. Remove from likedNotebooksContent after matching the path, update isLikedNotebook flag
|
||||
|
||||
currentOfficialSamplesObject.isLikedNotebook = false;
|
||||
const likedNotebookIndex = currentLikedNotebooks.findIndex((path: string) => {
|
||||
return path === currentOfficialSamplesObject.path;
|
||||
});
|
||||
currentLikedNotebooksContent.splice(likedNotebookIndex, 1);
|
||||
currentLikedNotebooks.splice(likedNotebookIndex, 1);
|
||||
currentUserMetadata = { likedNotebooks: currentLikedNotebooks };
|
||||
}
|
||||
|
||||
currentOfficialSamplesContent[officialSamplesIndex] = currentOfficialSamplesObject;
|
||||
|
||||
this.setState({
|
||||
activeTabIndex: 0,
|
||||
userMetadata: currentUserMetadata,
|
||||
likedNotebooksContent: currentLikedNotebooksContent,
|
||||
officialSamplesContent: currentOfficialSamplesContent
|
||||
});
|
||||
|
||||
JunoUtils.updateNotebookMetadata(this.authorizationToken, notebookMetadata).then(
|
||||
async returnedNotebookMetadata => {
|
||||
if (metadataLikesUpdates !== 0) {
|
||||
JunoUtils.updateUserMetadata(this.authorizationToken, currentUserMetadata);
|
||||
// TODO: update state here?
|
||||
}
|
||||
},
|
||||
error => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error updating notebook metadata: ${JSON.stringify(error)}`
|
||||
);
|
||||
// TODO add telemetry
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private onTabIndexChange = (activeTabIndex: number) => this.setState({ activeTabIndex });
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<TabComponent.TabComponent
|
||||
tabs={this.appTabs}
|
||||
onTabIndexChange={this.onTabIndexChange.bind(this)}
|
||||
currentTabIndex={this.state.activeTabIndex}
|
||||
hideHeader={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface GalleryViewerContainerComponentProps {
|
||||
container: ViewModels.Explorer;
|
||||
}
|
||||
|
||||
interface GalleryViewerContainerComponentState {
|
||||
officialSamplesData: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebooksData: DataModels.LikedNotebooksJunoResponse;
|
||||
}
|
||||
|
||||
export class GalleryViewerContainerComponent extends React.Component<
|
||||
GalleryViewerContainerComponentProps,
|
||||
GalleryViewerContainerComponentState
|
||||
> {
|
||||
constructor(props: GalleryViewerContainerComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
officialSamplesData: undefined,
|
||||
likedNotebooksData: undefined
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const authToken = CosmosClient.authorizationToken();
|
||||
JunoUtils.getOfficialSampleNotebooks(authToken).then(
|
||||
(data1: DataModels.GitHubInfoJunoResponse[]) => {
|
||||
const officialSamplesData = data1;
|
||||
|
||||
JunoUtils.getLikedNotebooks(authToken).then(
|
||||
(data2: DataModels.LikedNotebooksJunoResponse) => {
|
||||
const likedNotebooksData = data2;
|
||||
|
||||
officialSamplesData.map((value: DataModels.GitHubInfoJunoResponse, index: number) => {
|
||||
value.officialSamplesIndex = index;
|
||||
value.isLikedNotebook = likedNotebooksData.userMetadata.likedNotebooks.includes(value.path);
|
||||
});
|
||||
|
||||
likedNotebooksData.likedNotebooksContent.map((value: DataModels.GitHubInfoJunoResponse) => {
|
||||
value.isLikedNotebook = true;
|
||||
value.officialSamplesIndex = officialSamplesData.findIndex(
|
||||
(officialSample: DataModels.GitHubInfoJunoResponse) => {
|
||||
return officialSample.path === value.path;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
officialSamplesData: officialSamplesData,
|
||||
likedNotebooksData: likedNotebooksData
|
||||
});
|
||||
},
|
||||
error => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error fetching liked notebooks: ${JSON.stringify(error)}`
|
||||
);
|
||||
// TODO Add telemetry
|
||||
}
|
||||
);
|
||||
},
|
||||
error => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error fetching sample notebooks: ${JSON.stringify(error)}`
|
||||
);
|
||||
// TODO Add telemetry
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return this.state.officialSamplesData && this.state.likedNotebooksData ? (
|
||||
<GalleryViewerComponent
|
||||
container={this.props.container}
|
||||
officialSamplesData={this.state.officialSamplesData}
|
||||
likedNotebookData={this.state.likedNotebooksData}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface GalleryViewerComponentProps {
|
||||
container: ViewModels.Explorer;
|
||||
officialSamplesData: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebookData: DataModels.LikedNotebooksJunoResponse;
|
||||
}
|
||||
|
||||
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return this.props.container ? (
|
||||
<div className="galleryContainer">
|
||||
<FullWidthTabs
|
||||
officialSamplesContent={this.props.officialSamplesData}
|
||||
likedNotebooksContent={this.props.likedNotebookData.likedNotebooksContent}
|
||||
userMetadata={this.props.likedNotebookData.userMetadata}
|
||||
onClick={this.openNotebookViewer}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="galleryContainer">
|
||||
<GalleryCardsComponent
|
||||
data={this.props.officialSamplesData}
|
||||
onClick={this.openNotebookViewer}
|
||||
userMetadata={undefined}
|
||||
onNotebookMetadataChange={undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public getOfficialSamplesData(): DataModels.GitHubInfoJunoResponse[] {
|
||||
return this.props.officialSamplesData;
|
||||
}
|
||||
|
||||
public getLikedNotebookData(): DataModels.LikedNotebooksJunoResponse {
|
||||
return this.props.likedNotebookData;
|
||||
}
|
||||
|
||||
public openNotebookViewer = async (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => {
|
||||
if (!this.props.container) {
|
||||
SessionStorageUtility.setEntryString(
|
||||
StorageKey.NotebookMetadata,
|
||||
notebookMetadata ? JSON.stringify(notebookMetadata) : null
|
||||
);
|
||||
SessionStorageUtility.setEntryString(StorageKey.NotebookName, path.basename(url));
|
||||
window.open(`${config.hostedExplorerURL}notebookViewer.html?notebookurl=${url}`, "_blank");
|
||||
} else {
|
||||
this.props.container.openNotebookViewer(url, notebookMetadata, onNotebookMetadataChange, isLikedNotebook);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FullWidthTabs renders 1`] = `
|
||||
<TabComponent
|
||||
currentTabIndex={0}
|
||||
hideHeader={false}
|
||||
onTabIndexChange={[Function]}
|
||||
tabs={
|
||||
Array [
|
||||
Object {
|
||||
"content": Object {
|
||||
"className": "",
|
||||
"render": [Function],
|
||||
},
|
||||
"isVisible": [Function],
|
||||
"title": "Official Samples",
|
||||
},
|
||||
Object {
|
||||
"content": Object {
|
||||
"className": "",
|
||||
"render": [Function],
|
||||
},
|
||||
"isVisible": [Function],
|
||||
"title": "Liked Notebooks",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`GalleryCardComponent renders 1`] = `
|
||||
<div
|
||||
className="galleryContainer"
|
||||
>
|
||||
<GalleryCardsComponent
|
||||
data={Array []}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`GalleryCardsComponent renders 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 30,
|
||||
}
|
||||
}
|
||||
wrap={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`GalleryViewerContainerComponent renders 1`] = `<Fragment />`;
|
||||
@@ -1,11 +0,0 @@
|
||||
.notebookViewerMetadataContainer {
|
||||
margin: 0px 10px;
|
||||
|
||||
.title, .decoration, .persona {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.extras {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { NotebookMetadataComponentProps, NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import { NotebookMetadata } from "../../../Contracts/DataModels";
|
||||
|
||||
describe("NotebookMetadataComponent", () => {
|
||||
it("renders un-liked notebook", () => {
|
||||
const props: NotebookMetadataComponentProps = {
|
||||
notebookName: "My notebook",
|
||||
container: undefined,
|
||||
notebookMetadata: undefined,
|
||||
notebookContent: {},
|
||||
onNotebookMetadataChange: (newNotebookMetadata: NotebookMetadata) => Promise.resolve(),
|
||||
isLikedNotebook: false
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders liked notebook", () => {
|
||||
const props: NotebookMetadataComponentProps = {
|
||||
notebookName: "My notebook",
|
||||
container: undefined,
|
||||
notebookMetadata: undefined,
|
||||
notebookContent: {},
|
||||
onNotebookMetadataChange: (newNotebookMetadata: NotebookMetadata) => Promise.resolve(),
|
||||
isLikedNotebook: true
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO Add test for metadata display
|
||||
});
|
||||
@@ -6,97 +6,47 @@ import * as React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { NotebookMetadata } from "../../../Contracts/DataModels";
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
import { Icon, Persona, Text, IconButton } from "office-ui-fabric-react";
|
||||
import { Icon, Persona, Text } from "office-ui-fabric-react";
|
||||
import CSS from "csstype";
|
||||
import {
|
||||
siteTextStyles,
|
||||
subtleIconStyles,
|
||||
iconStyles,
|
||||
iconButtonStyles,
|
||||
mainHelpfulTextStyles,
|
||||
subtleHelpfulTextStyles,
|
||||
helpfulTextStyles
|
||||
} from "../NotebookGallery/Cards/CardStyleConstants";
|
||||
|
||||
import "./NotebookViewerComponent.less";
|
||||
} from "../../../GalleryViewer/Cards/CardStyleConstants";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
export interface NotebookMetadataComponentProps {
|
||||
interface NotebookMetadataComponentProps {
|
||||
notebookName: string;
|
||||
container: ViewModels.Explorer;
|
||||
notebookMetadata: NotebookMetadata;
|
||||
notebookContent: any;
|
||||
onNotebookMetadataChange: (newNotebookMetadata: NotebookMetadata) => Promise<void>;
|
||||
isLikedNotebook: boolean;
|
||||
}
|
||||
|
||||
interface NotebookMetadatComponentState {
|
||||
liked: boolean;
|
||||
notebookMetadata: NotebookMetadata;
|
||||
}
|
||||
|
||||
export class NotebookMetadataComponent extends React.Component<
|
||||
NotebookMetadataComponentProps,
|
||||
NotebookMetadatComponentState
|
||||
> {
|
||||
constructor(props: NotebookMetadataComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
liked: this.props.isLikedNotebook,
|
||||
notebookMetadata: this.props.notebookMetadata
|
||||
};
|
||||
}
|
||||
|
||||
private onDownloadClick = (newNotebookName: string) => {
|
||||
this.props.container
|
||||
.importAndOpenFromGallery(this.props.notebookName, newNotebookName, JSON.stringify(this.props.notebookContent))
|
||||
.then(() => {
|
||||
if (this.props.notebookMetadata) {
|
||||
if (this.props.onNotebookMetadataChange) {
|
||||
const notebookMetadata = { ...this.state.notebookMetadata };
|
||||
notebookMetadata.downloads += 1;
|
||||
this.props.onNotebookMetadataChange(notebookMetadata).then(() => {
|
||||
this.setState({ notebookMetadata: notebookMetadata });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
|
||||
private inlineBlockStyle: CSS.Properties = {
|
||||
display: "inline-block"
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.onNotebookMetadataChange) {
|
||||
const notebookMetadata = { ...this.state.notebookMetadata };
|
||||
if (this.props.notebookMetadata) {
|
||||
notebookMetadata.views += 1;
|
||||
this.props.onNotebookMetadataChange(notebookMetadata).then(() => {
|
||||
this.setState({ notebookMetadata: notebookMetadata });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onLike = (): void => {
|
||||
if (this.props.onNotebookMetadataChange) {
|
||||
const notebookMetadata = { ...this.state.notebookMetadata };
|
||||
let liked: boolean;
|
||||
if (this.state.liked) {
|
||||
liked = false;
|
||||
notebookMetadata.likes -= 1;
|
||||
} else {
|
||||
liked = true;
|
||||
notebookMetadata.likes += 1;
|
||||
}
|
||||
|
||||
this.props.onNotebookMetadataChange(notebookMetadata).then(() => {
|
||||
this.setState({ liked: liked, notebookMetadata: notebookMetadata });
|
||||
});
|
||||
}
|
||||
private marginTopStyle: CSS.Properties = {
|
||||
marginTop: "5px"
|
||||
};
|
||||
|
||||
private onDownload = (): void => {
|
||||
private onDownloadClick: (newNotebookName: string) => void = (newNotebookName: string) => {
|
||||
this.props.container.importAndOpenFromGallery(
|
||||
this.props.notebookName,
|
||||
newNotebookName,
|
||||
JSON.stringify(this.props.notebookContent)
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
const promptForNotebookName = () => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
let newNotebookName = this.props.notebookName;
|
||||
var newNotebookName = this.props.notebookName;
|
||||
this.props.container.showOkCancelTextFieldModalDialog(
|
||||
"Save notebook as",
|
||||
undefined,
|
||||
@@ -118,35 +68,27 @@ export class NotebookMetadataComponent extends React.Component<
|
||||
});
|
||||
};
|
||||
|
||||
promptForNotebookName().then((newNotebookName: string) => {
|
||||
this.onDownloadClick(newNotebookName);
|
||||
});
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="notebookViewerMetadataContainer">
|
||||
<h3 className="title">{this.props.notebookName}</h3>
|
||||
<h3 style={this.inlineBlockStyle}>{this.props.notebookName}</h3>
|
||||
|
||||
{this.props.notebookMetadata && (
|
||||
<div className="decoration">
|
||||
{this.props.container ? (
|
||||
<IconButton
|
||||
iconProps={{ iconName: this.state.liked ? "HeartFill" : "Heart" }}
|
||||
styles={iconButtonStyles}
|
||||
onClick={this.onLike}
|
||||
/>
|
||||
) : (
|
||||
<Icon iconName="Heart" styles={iconStyles} />
|
||||
)}
|
||||
<Text variant="large" styles={mainHelpfulTextStyles}>
|
||||
{this.state.notebookMetadata.likes} likes
|
||||
<div style={this.inlineBlockStyle}>
|
||||
<Icon iconName="Heart" styles={iconStyles} />
|
||||
<Text variant="medium" styles={mainHelpfulTextStyles}>
|
||||
{this.props.notebookMetadata.likes} likes
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.props.container && (
|
||||
<button aria-label="downloadButton" className="downloadButton" onClick={this.onDownload}>
|
||||
<button
|
||||
aria-label="downloadButton"
|
||||
className="downloadButton"
|
||||
onClick={async () => {
|
||||
promptForNotebookName().then(this.onDownloadClick);
|
||||
}}
|
||||
>
|
||||
Download Notebook
|
||||
</button>
|
||||
)}
|
||||
@@ -155,20 +97,20 @@ export class NotebookMetadataComponent extends React.Component<
|
||||
<>
|
||||
<div>
|
||||
<Persona
|
||||
className="persona"
|
||||
style={this.inlineBlockStyle}
|
||||
text={this.props.notebookMetadata.author}
|
||||
secondaryText={this.props.notebookMetadata.date}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="extras">
|
||||
<div style={this.marginTopStyle}>
|
||||
<Icon iconName="RedEye" styles={subtleIconStyles} />
|
||||
<Text variant="small" styles={subtleHelpfulTextStyles}>
|
||||
{this.state.notebookMetadata.views}
|
||||
{this.props.notebookMetadata.views}
|
||||
</Text>
|
||||
<Icon iconName="Download" styles={subtleIconStyles} />
|
||||
<Text variant="small" styles={subtleHelpfulTextStyles}>
|
||||
{this.state.notebookMetadata.downloads}
|
||||
{this.props.notebookMetadata.downloads}
|
||||
</Text>
|
||||
</div>
|
||||
<Text variant="small" styles={siteTextStyles}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import { NotebookMetadata } from "../Contracts/DataModels";
|
||||
import { NotebookViewerComponent } from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent";
|
||||
import { SessionStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import { NotebookMetadata } from "../../../Contracts/DataModels";
|
||||
import { NotebookViewerComponent } from "./NotebookViewerComponent";
|
||||
import { SessionStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||
|
||||
const getNotebookUrl = (): string => {
|
||||
const regex: RegExp = new RegExp("[?&]notebookurl=([^&#]*)|&|#|$");
|
||||
@@ -26,14 +26,12 @@ const onInit = async () => {
|
||||
SessionStorageUtility.removeEntry(StorageKey.NotebookName);
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const notebookViewerComponent = (
|
||||
<NotebookViewerComponent
|
||||
notebookMetadata={notebookMetadata}
|
||||
notebookName={notebookName}
|
||||
notebookUrl={getNotebookUrl()}
|
||||
hideInputs={urlParams.get("hideinputs") === "true"}
|
||||
container={null}
|
||||
/>
|
||||
);
|
||||
ReactDOM.render(notebookViewerComponent, document.getElementById("notebookContent"));
|
||||
@@ -4,7 +4,7 @@
|
||||
padding: @DefaultSpace;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.downloadButton {
|
||||
@@ -20,7 +20,7 @@
|
||||
display: "inline-block";
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
.active, .downloadButton:hover {
|
||||
color: @BaseMedium;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@ import "./NotebookViewerComponent.less";
|
||||
export interface NotebookViewerComponentProps {
|
||||
notebookName: string;
|
||||
notebookUrl: string;
|
||||
container?: ViewModels.Explorer;
|
||||
container: ViewModels.Explorer;
|
||||
notebookMetadata: NotebookMetadata;
|
||||
onNotebookMetadataChange?: (newNotebookMetadata: NotebookMetadata) => Promise<void>;
|
||||
isLikedNotebook?: boolean;
|
||||
hideInputs?: boolean;
|
||||
}
|
||||
|
||||
interface NotebookViewerComponentState {
|
||||
element: JSX.Element;
|
||||
content: any;
|
||||
}
|
||||
|
||||
@@ -52,7 +50,7 @@ export class NotebookViewerComponent extends React.Component<
|
||||
contentRef: createContentRef()
|
||||
});
|
||||
|
||||
this.state = { content: undefined };
|
||||
this.state = { element: undefined, content: undefined };
|
||||
}
|
||||
|
||||
private async getJsonNotebookContent(): Promise<any> {
|
||||
@@ -67,25 +65,24 @@ export class NotebookViewerComponent extends React.Component<
|
||||
componentDidMount() {
|
||||
this.getJsonNotebookContent().then((jsonContent: any) => {
|
||||
this.notebookComponentBootstrapper.setContent("json", jsonContent);
|
||||
this.setState({ content: jsonContent });
|
||||
const notebookReadonlyComponent = this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer);
|
||||
this.setState({ element: notebookReadonlyComponent, content: jsonContent });
|
||||
});
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
return this.state != null ? (
|
||||
<div className="notebookViewerContainer">
|
||||
<NotebookMetadataComponent
|
||||
notebookMetadata={this.props.notebookMetadata}
|
||||
notebookName={this.props.notebookName}
|
||||
container={this.props.container}
|
||||
notebookContent={this.state.content}
|
||||
onNotebookMetadataChange={this.props.onNotebookMetadataChange}
|
||||
isLikedNotebook={this.props.isLikedNotebook}
|
||||
/>
|
||||
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
|
||||
hideInputs: this.props.hideInputs
|
||||
})}
|
||||
{this.state.element}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<div
|
||||
className="notebookViewerMetadataContainer"
|
||||
>
|
||||
<h3
|
||||
className="title"
|
||||
>
|
||||
My notebook
|
||||
</h3>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<div
|
||||
className="notebookViewerMetadataContainer"
|
||||
>
|
||||
<h3
|
||||
className="title"
|
||||
>
|
||||
My notebook
|
||||
</h3>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,28 +1,27 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.tabComponentContainer {
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
.tabSwitch {
|
||||
margin-left: @LargeSpace;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.tabSwitch {
|
||||
margin-left: @LargeSpace;
|
||||
margin-bottom: 20px;
|
||||
.tab {
|
||||
margin-right: @MediumSpace;
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-right: @MediumSpace;
|
||||
}
|
||||
.toggleSwitch {
|
||||
.toggleSwitch();
|
||||
}
|
||||
|
||||
.toggleSwitch {
|
||||
.toggleSwitch();
|
||||
}
|
||||
.selectedToggle {
|
||||
.selectedToggle();
|
||||
}
|
||||
|
||||
.selectedToggle {
|
||||
.selectedToggle();
|
||||
}
|
||||
|
||||
.unselectedToggle {
|
||||
.unselectedToggle();
|
||||
}
|
||||
}
|
||||
.unselectedToggle {
|
||||
.unselectedToggle();
|
||||
}
|
||||
}
|
||||
|
||||
.tabComponentContent {
|
||||
height: calc(100% - 20px);
|
||||
.flex-display();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import "./TabComponent.less";
|
||||
|
||||
export interface TabContent {
|
||||
render: () => JSX.Element;
|
||||
@@ -76,10 +75,10 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tabComponentContainer">
|
||||
<React.Fragment>
|
||||
{!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
|
||||
<div className={className}>{currentTabContent.render()}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,7 +1143,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
isModal: true,
|
||||
visible: true,
|
||||
title: `Enable Azure Synapse Link on your Cosmos DB account`,
|
||||
subText: `Enable Azure Synapse Link to perform near real time analytical analytics on this account, without impacting the performance of your transactional workloads.
|
||||
subText: `Enable Azure Synapse Link to perform near real time analytical analytics on this account, without impacting the performance of your transactional workloads.
|
||||
Azure Synapse Link brings together Cosmos Db Analytical Store and Synapse Analytics`,
|
||||
primaryButtonText: "Enable Azure Synapse Link",
|
||||
secondaryButtonText: "Cancel",
|
||||
@@ -2583,7 +2583,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
const item = NotebookUtil.createNotebookContentItem(name, path, "file");
|
||||
const parent = this.resourceTree.myNotebooksContentRoot;
|
||||
|
||||
if (parent && parent.children && this.isNotebookEnabled() && this.notebookClient) {
|
||||
if (parent && this.isNotebookEnabled() && this.notebookClient) {
|
||||
if (this._filePathToImportAndOpen === path) {
|
||||
this._filePathToImportAndOpen = null; // we don't want to try opening this path again
|
||||
}
|
||||
@@ -3278,12 +3278,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
newTab.onTabClick();
|
||||
}
|
||||
|
||||
public openNotebookViewer(
|
||||
notebookUrl: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) {
|
||||
public openNotebookViewer(notebookUrl: string, notebookMetadata: DataModels.NotebookMetadata) {
|
||||
const notebookName = path.basename(notebookUrl);
|
||||
const title = notebookName;
|
||||
const hashLocation = notebookUrl;
|
||||
@@ -3325,9 +3320,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
openedTabs: this.openedTabs(),
|
||||
notebookUrl: notebookUrl,
|
||||
notebookName: notebookName,
|
||||
notebookMetadata: notebookMetadata,
|
||||
onNotebookMetadataChange: onNotebookMetadataChange,
|
||||
isLikedNotebook: isLikedNotebook
|
||||
notebookMetadata: notebookMetadata
|
||||
});
|
||||
|
||||
this.openedTabs.push(newTab);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "underscore";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Observable } from "knockout";
|
||||
@@ -47,7 +46,7 @@ export class CommandBarUtil {
|
||||
text: btn.commandButtonLabel || btn.tooltipText,
|
||||
"data-test": btn.commandButtonLabel || btn.tooltipText,
|
||||
title: btn.tooltipText,
|
||||
name: btn.commandButtonLabel || btn.tooltipText,
|
||||
name: "menuitem",
|
||||
disabled: btn.disabled,
|
||||
ariaLabel: btn.ariaLabel,
|
||||
buttonStyles: {
|
||||
@@ -127,9 +126,6 @@ export class CommandBarUtil {
|
||||
}
|
||||
|
||||
if (btn.isDropdown) {
|
||||
const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
|
||||
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
|
||||
|
||||
const dropdownStyles: Partial<IDropdownStyles> = {
|
||||
root: { margin: 5 },
|
||||
dropdown: { width: btn.dropdownWidth },
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { ContentRef } from "@nteract/core";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { CellId } from "@nteract/commutable";
|
||||
|
||||
export const CLOSE_NOTEBOOK = "CLOSE_NOTEBOOK";
|
||||
export interface CloseNotebookAction {
|
||||
@@ -17,6 +16,25 @@ export const closeNotebook = (payload: { contentRef: ContentRef }): CloseNoteboo
|
||||
};
|
||||
};
|
||||
|
||||
export const UPDATE_LAST_MODIFIED = "UPDATE_LAST_MODIFIED";
|
||||
export interface UpdateLastModifiedAction {
|
||||
type: "UPDATE_LAST_MODIFIED";
|
||||
payload: {
|
||||
contentRef: ContentRef;
|
||||
lastModified: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const updateLastModified = (payload: {
|
||||
contentRef: ContentRef;
|
||||
lastModified: string;
|
||||
}): UpdateLastModifiedAction => {
|
||||
return {
|
||||
type: UPDATE_LAST_MODIFIED,
|
||||
payload
|
||||
};
|
||||
};
|
||||
|
||||
export const EXECUTE_FOCUSED_CELL_AND_FOCUS_NEXT = "EXECUTE_FOCUSED_CELL_AND_FOCUS_NEXT";
|
||||
export interface ExecuteFocusedCellAndFocusNextAction {
|
||||
type: "EXECUTE_FOCUSED_CELL_AND_FOCUS_NEXT";
|
||||
@@ -63,24 +81,3 @@ export const setHoveredCell = (payload: { cellId: CellId }): SetHoveredCellActio
|
||||
payload
|
||||
};
|
||||
};
|
||||
|
||||
export const TRACE_NOTEBOOK_TELEMETRY = "TRACE_NOTEBOOK_TELEMETRY";
|
||||
export interface TraceNotebookTelemetryAction {
|
||||
type: "TRACE_NOTEBOOK_TELEMETRY";
|
||||
payload: {
|
||||
action: Action;
|
||||
actionModifier?: string;
|
||||
data?: any;
|
||||
};
|
||||
}
|
||||
|
||||
export const traceNotebookTelemetry = (payload: {
|
||||
action: Action;
|
||||
actionModifier?: string;
|
||||
data?: any;
|
||||
}): TraceNotebookTelemetryAction => {
|
||||
return {
|
||||
type: TRACE_NOTEBOOK_TELEMETRY,
|
||||
payload
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||
import { empty, merge, of, timer, interval, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||
import { webSocket } from "rxjs/webSocket";
|
||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||
import { ofType } from "redux-observable";
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
map,
|
||||
switchMap,
|
||||
take,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
catchError,
|
||||
first,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
AppState,
|
||||
ServerConfig as JupyterServerConfig,
|
||||
JupyterHostRecordProps,
|
||||
JupyterHostRecord,
|
||||
RemoteKernelProps,
|
||||
castToSessionId,
|
||||
createKernelRef,
|
||||
@@ -27,7 +29,8 @@ import {
|
||||
ContentRef,
|
||||
KernelInfo,
|
||||
actions,
|
||||
selectors
|
||||
selectors,
|
||||
IContentProvider
|
||||
} from "@nteract/core";
|
||||
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
|
||||
import { sessions, kernels } from "rx-jupyter";
|
||||
@@ -749,6 +752,69 @@ export const cleanKernelOnConnectionLostEpic = (
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Workaround for issue: https://github.com/nteract/nteract/issues/4583
|
||||
* We reajust the property
|
||||
* @param action$
|
||||
* @param state$
|
||||
*/
|
||||
const adjustLastModifiedOnSaveEpic = (
|
||||
action$: ActionsObservable<actions.SaveFulfilled>,
|
||||
state$: StateObservable<AppState>,
|
||||
dependencies: { contentProvider: IContentProvider }
|
||||
): Observable<{} | CdbActions.UpdateLastModifiedAction> => {
|
||||
return action$.pipe(
|
||||
ofType(actions.SAVE_FULFILLED),
|
||||
mergeMap(action => {
|
||||
const pollDelayMs = 500;
|
||||
const nbAttempts = 4;
|
||||
|
||||
// Retry updating last modified
|
||||
const currentHost = selectors.currentHost(state$.value);
|
||||
const serverConfig = selectors.serverConfig(currentHost as JupyterHostRecord);
|
||||
const filepath = selectors.filepath(state$.value, { contentRef: action.payload.contentRef });
|
||||
const content = selectors.content(state$.value, { contentRef: action.payload.contentRef });
|
||||
const lastSaved = (content.lastSaved as any) as string;
|
||||
const contentProvider = dependencies.contentProvider;
|
||||
|
||||
// Query until value is stable
|
||||
return interval(pollDelayMs)
|
||||
.pipe(take(nbAttempts))
|
||||
.pipe(
|
||||
mergeMap(x =>
|
||||
contentProvider.get(serverConfig, filepath, { content: 0 }).pipe(
|
||||
map(xhr => {
|
||||
if (xhr.status !== 200 || typeof xhr.response === "string") {
|
||||
return undefined;
|
||||
}
|
||||
const model = xhr.response;
|
||||
const lastModified = model.last_modified;
|
||||
if (lastModified === lastSaved) {
|
||||
return undefined;
|
||||
}
|
||||
// Return last modified
|
||||
return lastModified;
|
||||
})
|
||||
)
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
mergeMap(lastModified => {
|
||||
if (!lastModified) {
|
||||
return empty();
|
||||
}
|
||||
|
||||
return of(
|
||||
CdbActions.updateLastModified({
|
||||
contentRef: action.payload.contentRef,
|
||||
lastModified
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute focused cell and focus next cell
|
||||
* @param action$
|
||||
@@ -851,6 +917,7 @@ export const allEpics = [
|
||||
acquireKernelInfoEpic,
|
||||
handleKernelConnectionLostEpic,
|
||||
cleanKernelOnConnectionLostEpic,
|
||||
adjustLastModifiedOnSaveEpic,
|
||||
executeFocusedCellAndFocusNextEpic,
|
||||
closeUnsupportedMimetypesEpic,
|
||||
closeContentFailedToFetchEpic,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||
import { Action } from "redux";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as cdbActions from "./actions";
|
||||
import { CdbRecord } from "./types";
|
||||
|
||||
import { Action } from "redux";
|
||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||
|
||||
export const coreReducer = (state: CoreRecord, action: Action) => {
|
||||
let typedAction;
|
||||
switch (action.type) {
|
||||
@@ -51,6 +50,11 @@ export const coreReducer = (state: CoreRecord, action: Action) => {
|
||||
.setIn(path.concat("displayName"), kernelspecs.displayName)
|
||||
.setIn(path.concat("language"), kernelspecs.language);
|
||||
}
|
||||
case cdbActions.UPDATE_LAST_MODIFIED: {
|
||||
typedAction = action as cdbActions.UpdateLastModifiedAction;
|
||||
const path = ["entities", "contents", "byRef", typedAction.payload.contentRef, "lastSaved"];
|
||||
return state.setIn(path, typedAction.payload.lastModified);
|
||||
}
|
||||
default:
|
||||
return nteractReducers.core(state as any, action as any);
|
||||
}
|
||||
@@ -71,17 +75,6 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
||||
const typedAction = action as cdbActions.SetHoveredCellAction;
|
||||
return state.set("hoveredCellId", typedAction.payload.cellId);
|
||||
}
|
||||
|
||||
case cdbActions.TRACE_NOTEBOOK_TELEMETRY: {
|
||||
const typedAction = action as cdbActions.TraceNotebookTelemetryAction;
|
||||
TelemetryProcessor.trace(typedAction.payload.action, typedAction.payload.actionModifier, {
|
||||
...typedAction.payload.data,
|
||||
databaseAccountName: state.databaseAccountName,
|
||||
defaultExperience: state.defaultExperience,
|
||||
dataExplorerArea: Areas.Notebook
|
||||
});
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -9,12 +9,11 @@ import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import { actions, ContentRef } from "@nteract/core";
|
||||
import loadTransform from "../NotebookComponent/loadTransform";
|
||||
import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror";
|
||||
import CodeMirrorEditor from "@nteract/editor";
|
||||
import "./NotebookReadOnlyRenderer.less";
|
||||
|
||||
export interface NotebookRendererProps {
|
||||
contentRef: any;
|
||||
hideInputs?: boolean;
|
||||
}
|
||||
|
||||
interface PassedEditorProps {
|
||||
@@ -47,8 +46,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<CodeCell id={id} contentRef={contentRef}>
|
||||
{{
|
||||
editor: {
|
||||
codemirror: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
||||
codemirror: (props: PassedEditorProps) => <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
||||
},
|
||||
prompt: ({ id, contentRef }) => <></>
|
||||
}}
|
||||
@@ -65,8 +63,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
codemirror: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
||||
codemirror: (props: PassedEditorProps) => <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
||||
}
|
||||
}}
|
||||
</RawCell>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { actions, ContentRef, selectors } from "@nteract/core";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
|
||||
import { ContentRef, selectors, actions } from "@nteract/core";
|
||||
import { CdbAppState } from "../NotebookComponent/types";
|
||||
|
||||
export interface PassedPromptProps {
|
||||
@@ -84,15 +83,7 @@ const mapDispatchToProps = (
|
||||
dispatch: Dispatch,
|
||||
{ id, contentRef }: { id: string; contentRef: ContentRef }
|
||||
): DispatchProps => ({
|
||||
executeCell: () => {
|
||||
dispatch(actions.executeCell({ id, contentRef }));
|
||||
dispatch(
|
||||
cdbActions.traceNotebookTelemetry({
|
||||
action: Action.ExecuteCellPromptBtn,
|
||||
actionModifier: ActionModifiers.Mark
|
||||
})
|
||||
);
|
||||
},
|
||||
executeCell: () => dispatch(actions.executeCell({ id, contentRef })),
|
||||
stopExecution: () => dispatch(actions.interruptKernel({}))
|
||||
});
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { IGitHubRepo, IGitHubBranch } from "../../GitHub/GitHubClient";
|
||||
|
||||
export const SamplesRepo: IGitHubRepo = {
|
||||
name: "cosmos-notebooks",
|
||||
owner: "Azure-Samples",
|
||||
private: false
|
||||
};
|
||||
|
||||
export const SamplesBranch: IGitHubBranch = {
|
||||
name: "master"
|
||||
};
|
||||
|
||||
export const isSamplesCall = (owner: string, repo: string, branch?: string): boolean => {
|
||||
return owner === SamplesRepo.owner && repo === SamplesRepo.name && (!branch || branch === SamplesBranch.name);
|
||||
};
|
||||
|
||||
// GitHub API calls have a rate limit of 5000 requests per hour. So if we get high traffic on Data Explorer
|
||||
// loading samples exceed that limit. Using this hard coded response for samples until we fix that.
|
||||
export const SamplesContentsQueryResponse = {
|
||||
repository: {
|
||||
owner: {
|
||||
login: "Azure-Samples"
|
||||
},
|
||||
name: "cosmos-notebooks",
|
||||
isPrivate: false,
|
||||
ref: {
|
||||
name: "master",
|
||||
target: {
|
||||
history: {
|
||||
nodes: [
|
||||
{
|
||||
oid: "cda7facb9e039b173f3376200c26c859896e7974",
|
||||
message:
|
||||
"Merge pull request #45 from Azure-Samples/users/deborahc/pythonSampleUpdates\n\nAdd bokeh version to notebook",
|
||||
committer: {
|
||||
date: "2020-05-28T11:28:01-07:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
object: {
|
||||
entries: [
|
||||
{
|
||||
name: ".github",
|
||||
type: "tree",
|
||||
object: {}
|
||||
},
|
||||
{
|
||||
name: ".gitignore",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "3e759b75bf455ac809d0987d369aab89137b5689",
|
||||
byteSize: 5582
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "1. GettingStarted.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "0732ff5366e4aefdc4c378c61cbd968664f0acec",
|
||||
byteSize: 3933
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "2. Visualization.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "6b16b0740a77afdd38a95bc6c3ebd0f2f17d9465",
|
||||
byteSize: 820317
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "3. RequestUnits.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "252b79a4adc81e9f2ffde453231b695d75e270e8",
|
||||
byteSize: 9490
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "4. Indexing.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "e10dd67bd1c55c345226769e4f80e43659ef9cd5",
|
||||
byteSize: 10394
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "5. StoredProcedures.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "949941949920de4d2d111149e2182e9657cc8134",
|
||||
byteSize: 11818
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "6. GlobalDistribution.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "b91c31dacacbc9e35750d9054063dda4a5309f3b",
|
||||
byteSize: 11375
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "7. IoTAnomalyDetection.ipynb",
|
||||
type: "blob",
|
||||
object: {
|
||||
oid: "82057ae52a67721a5966e2361317f5dfbd0ee595",
|
||||
byteSize: 377939
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "All_API_quickstarts",
|
||||
type: "tree",
|
||||
object: {}
|
||||
},
|
||||
{
|
||||
name: "CSharp_quickstarts",
|
||||
type: "tree",
|
||||
object: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -155,7 +155,19 @@ function openPane(action: ActionContracts.OpenPane, explorer: ViewModels.Explore
|
||||
}
|
||||
|
||||
function openFile(action: ActionContracts.OpenSampleNotebook, explorer: ViewModels.Explorer) {
|
||||
explorer.handleOpenFileAction(decodeURIComponent(action.path));
|
||||
let path: string;
|
||||
if (action.hasOwnProperty("file")) {
|
||||
// This is deprecated
|
||||
const downloadUrl: string = (action as any).file.download_url;
|
||||
path = downloadUrl.replace(
|
||||
"raw.githubusercontent.com/Azure-Samples/cosmos-notebooks",
|
||||
"github.com/Azure-Samples/cosmos-notebooks/blob"
|
||||
); // convert raw download url to something which GitHubContentProvider understands
|
||||
} else {
|
||||
path = action.path;
|
||||
}
|
||||
|
||||
explorer.handleOpenFileAction(path);
|
||||
}
|
||||
|
||||
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperty: string): string {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -82,7 +81,7 @@ export class ClusterLibraryPane extends ContextualPaneBase {
|
||||
|
||||
private _onInstalledChanged = (libraryName: string, installed: boolean): void => {
|
||||
const items = this._clusterLibraryProps().libraryItems;
|
||||
const library = _.find(items, item => item.name === libraryName);
|
||||
const library = items.find(item => item.name === libraryName);
|
||||
library.installed = installed;
|
||||
this._clusterLibraryProps.valueHasMutated();
|
||||
};
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import _ from "underscore";
|
||||
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||
import { Logger } from "../../Common/Logger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||
import { GitHubClient, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { GitHubUtils } from "../../Utils/GitHubUtils";
|
||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
|
||||
import { AuthorizeAccessComponent } from "../Controls/GitHub/AuthorizeAccessComponent";
|
||||
import { GitHubReposComponent, GitHubReposComponentProps, RepoListItem } from "../Controls/GitHub/GitHubReposComponent";
|
||||
import { GitHubReposComponentProps, RepoListItem, GitHubReposComponent } from "../Controls/GitHub/GitHubReposComponent";
|
||||
import { GitHubReposComponentAdapter } from "../Controls/GitHub/GitHubReposComponentAdapter";
|
||||
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../Controls/GitHub/ReposListComponent";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||
|
||||
export class GitHubReposPane extends ContextualPaneBase {
|
||||
private static readonly PageSize = 30;
|
||||
@@ -30,7 +29,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
private gitHubReposAdapter: GitHubReposComponentAdapter;
|
||||
|
||||
private allGitHubRepos: IGitHubRepo[];
|
||||
private allGitHubReposLastPageInfo?: IGitHubPageInfo;
|
||||
private pinnedReposUpdated: boolean;
|
||||
|
||||
constructor(options: ViewModels.GitHubReposPaneOptions) {
|
||||
@@ -75,7 +73,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
this.gitHubReposAdapter = new GitHubReposComponentAdapter(this.gitHubReposProps);
|
||||
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
this.pinnedReposUpdated = false;
|
||||
}
|
||||
|
||||
@@ -118,7 +115,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
|
||||
// Reset cached repos
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
|
||||
// Reset flags
|
||||
this.pinnedReposUpdated = false;
|
||||
@@ -168,28 +164,29 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
const unpinnedGitHubRepos = this.allGitHubRepos.filter(
|
||||
gitHubRepo =>
|
||||
this.pinnedReposProps.repos.findIndex(
|
||||
pinnedRepo => pinnedRepo.key === GitHubUtils.toRepoFullName(gitHubRepo.owner, gitHubRepo.name)
|
||||
pinnedRepo => pinnedRepo.key === GitHubUtils.toRepoFullName(gitHubRepo.owner.login, gitHubRepo.name)
|
||||
) === -1
|
||||
);
|
||||
return unpinnedGitHubRepos.map(gitHubRepo => ({
|
||||
key: GitHubUtils.toRepoFullName(gitHubRepo.owner, gitHubRepo.name),
|
||||
key: GitHubUtils.toRepoFullName(gitHubRepo.owner.login, gitHubRepo.name),
|
||||
repo: gitHubRepo,
|
||||
branches: []
|
||||
}));
|
||||
}
|
||||
|
||||
private async loadMoreBranches(repo: IGitHubRepo): Promise<void> {
|
||||
const branchesProps = this.branchesProps[GitHubUtils.toRepoFullName(repo.owner, repo.name)];
|
||||
const branchesProps = this.branchesProps[GitHubUtils.toRepoFullName(repo.owner.login, repo.name)];
|
||||
branchesProps.hasMore = true;
|
||||
branchesProps.isLoading = true;
|
||||
this.triggerRender();
|
||||
|
||||
const nextPage = Math.floor(branchesProps.branches.length / GitHubReposPane.PageSize) + 1;
|
||||
try {
|
||||
const response = await this.gitHubClient.getBranchesAsync(
|
||||
repo.owner,
|
||||
repo.owner.login,
|
||||
repo.name,
|
||||
GitHubReposPane.PageSize,
|
||||
branchesProps.lastPageInfo?.endCursor
|
||||
nextPage,
|
||||
GitHubReposPane.PageSize
|
||||
);
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching branches`);
|
||||
@@ -197,7 +194,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
|
||||
if (response.data) {
|
||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||
branchesProps.lastPageInfo = response.pageInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to fetch branches: ${error}`;
|
||||
@@ -206,7 +202,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
branchesProps.isLoading = false;
|
||||
branchesProps.hasMore = branchesProps.lastPageInfo?.hasNextPage;
|
||||
branchesProps.hasMore = branchesProps.branches.length === GitHubReposPane.PageSize * nextPage;
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
@@ -215,18 +211,15 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
this.unpinnedReposProps.hasMore = true;
|
||||
this.triggerRender();
|
||||
|
||||
const nextPage = Math.floor(this.allGitHubRepos.length / GitHubReposPane.PageSize) + 1;
|
||||
try {
|
||||
const response = await this.gitHubClient.getReposAsync(
|
||||
GitHubReposPane.PageSize,
|
||||
this.allGitHubReposLastPageInfo?.endCursor
|
||||
);
|
||||
const response = await this.gitHubClient.getReposAsync(nextPage, GitHubReposPane.PageSize);
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching unpinned repos`);
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
this.allGitHubRepos = this.allGitHubRepos.concat(response.data);
|
||||
this.allGitHubReposLastPageInfo = response.pageInfo;
|
||||
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -236,7 +229,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
this.unpinnedReposProps.isLoading = false;
|
||||
this.unpinnedReposProps.hasMore = this.allGitHubReposLastPageInfo?.hasNextPage;
|
||||
this.unpinnedReposProps.hasMore = this.allGitHubRepos.length === GitHubReposPane.PageSize * nextPage;
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
@@ -260,7 +253,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
this.pinnedReposUpdated = true;
|
||||
const initialReposLength = this.pinnedReposProps.repos.length;
|
||||
|
||||
const existingRepo = _.find(this.pinnedReposProps.repos, repo => repo.key === item.key);
|
||||
const existingRepo = this.pinnedReposProps.repos.find(repo => repo.key === item.key);
|
||||
if (existingRepo) {
|
||||
existingRepo.branches = item.branches;
|
||||
} else {
|
||||
@@ -325,7 +318,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
if (!this.branchesProps[item.key]) {
|
||||
this.branchesProps[item.key] = {
|
||||
branches: [],
|
||||
lastPageInfo: undefined,
|
||||
hasMore: true,
|
||||
isLoading: true,
|
||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo)
|
||||
@@ -337,7 +329,6 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
|
||||
private async refreshUnpinnedRepoListItems(): Promise<void> {
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
this.unpinnedReposProps.repos = [];
|
||||
this.loadMoreUnpinnedRepos();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import { GalleryViewerContainerComponent } from "../Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import { GalleryViewerContainerComponent } from "../../GalleryViewer/GalleryViewerComponent";
|
||||
|
||||
/**
|
||||
* Notebook gallery tab
|
||||
|
||||
@@ -16,9 +16,7 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
|
||||
private notebookUrl: string,
|
||||
private notebookName: string,
|
||||
private container: ViewModels.Explorer,
|
||||
private notebookMetadata: DataModels.NotebookMetadata,
|
||||
private onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
private isLikedNotebook: boolean
|
||||
private notebookMetadata: DataModels.NotebookMetadata
|
||||
) {}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
@@ -28,8 +26,6 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
|
||||
notebookMetadata={this.notebookMetadata}
|
||||
notebookName={this.notebookName}
|
||||
container={this.container}
|
||||
onNotebookMetadataChange={this.onNotebookMetadataChange}
|
||||
isLikedNotebook={this.isLikedNotebook}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
@@ -50,9 +46,7 @@ export default class NotebookViewerTab extends TabsBase implements ViewModels.Ta
|
||||
options.notebookUrl,
|
||||
options.notebookName,
|
||||
options.container,
|
||||
options.notebookMetadata,
|
||||
options.onNotebookMetadataChange,
|
||||
options.isLikedNotebook
|
||||
options.notebookMetadata
|
||||
);
|
||||
|
||||
this.notebookViewerComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||
|
||||
@@ -32,7 +32,6 @@ import DocumentId from "./DocumentId";
|
||||
import StoredProcedure from "./StoredProcedure";
|
||||
import Trigger from "./Trigger";
|
||||
import UserDefinedFunction from "./UserDefinedFunction";
|
||||
import { config } from "../../Config";
|
||||
|
||||
export default class Collection implements ViewModels.Collection {
|
||||
public nodeKind: string;
|
||||
@@ -1417,7 +1416,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
masterKey: CosmosClient.masterKey(),
|
||||
endpoint: CosmosClient.endpoint(),
|
||||
accessToken: CosmosClient.accessToken(),
|
||||
platform: config.platform,
|
||||
platform: window.dataExplorerPlatform,
|
||||
databaseAccount: CosmosClient.databaseAccount()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import DeleteIcon from "../../../images/delete.svg";
|
||||
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
||||
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
||||
import { IGitHubRepo, IGitHubBranch } from "../../GitHub/GitHubClient";
|
||||
import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg";
|
||||
import FileIcon from "../../../images/notebook/file-cosmos.svg";
|
||||
import { ArrayHashMap } from "../../Common/ArrayHashMap";
|
||||
@@ -25,11 +26,21 @@ import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { Areas } from "../../Common/Constants";
|
||||
import { GitHubUtils } from "../../Utils/GitHubUtils";
|
||||
import { SamplesRepo, SamplesBranch } from "../Notebook/NotebookSamples";
|
||||
|
||||
export class ResourceTreeAdapter implements ReactAdapter {
|
||||
private static readonly DataTitle = "DATA";
|
||||
private static readonly NotebooksTitle = "NOTEBOOKS";
|
||||
|
||||
private static readonly SamplesRepo: IGitHubRepo = {
|
||||
name: "cosmos-notebooks",
|
||||
owner: {
|
||||
login: "Azure-Samples"
|
||||
},
|
||||
private: false
|
||||
};
|
||||
private static readonly SamplesBranch: IGitHubBranch = {
|
||||
name: "master"
|
||||
};
|
||||
private static readonly PseudoDirPath = "PsuedoDir";
|
||||
|
||||
public parameters: ko.Observable<number>;
|
||||
@@ -92,7 +103,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
this.sampleNotebooksContentRoot = {
|
||||
name: "Sample Notebooks (View Only)",
|
||||
path: GitHubUtils.toContentUri(SamplesRepo.owner, SamplesRepo.name, SamplesBranch.name, ""),
|
||||
path: GitHubUtils.toContentUri(
|
||||
ResourceTreeAdapter.SamplesRepo.owner.login,
|
||||
ResourceTreeAdapter.SamplesRepo.name,
|
||||
ResourceTreeAdapter.SamplesBranch.name,
|
||||
""
|
||||
),
|
||||
type: NotebookContentItemType.Directory
|
||||
};
|
||||
refreshTasks.push(
|
||||
|
||||
@@ -22,28 +22,11 @@ export const subtleHelpfulTextStyles: ITextStyles = {
|
||||
}
|
||||
};
|
||||
|
||||
export const iconButtonStyles: IIconStyles = {
|
||||
root: {
|
||||
marginLeft: "10px",
|
||||
color: "#0078D4",
|
||||
backgroundColor: "#FFF",
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeights.regular,
|
||||
display: "inline-block",
|
||||
selectors: {
|
||||
":hover .ms-Button-icon": {
|
||||
color: "#ccc"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const iconStyles: IIconStyles = {
|
||||
root: {
|
||||
marginLeft: "10px",
|
||||
color: "#0078D4",
|
||||
backgroundColor: "#FFF",
|
||||
fontSize: 16,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeights.regular,
|
||||
display: "inline-block"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Card, ICardTokens, ICardSectionTokens } from "@uifabric/react-cards";
|
||||
import { Icon, Image, Persona, Text } from "office-ui-fabric-react";
|
||||
import {
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
subtleIconStyles
|
||||
} from "./CardStyleConstants";
|
||||
|
||||
export interface GalleryCardComponentProps {
|
||||
interface GalleryCardComponentProps {
|
||||
name: string;
|
||||
url: string;
|
||||
notebookMetadata: DataModels.NotebookMetadata;
|
||||
9
src/GalleryViewer/GalleryViewer.less
Normal file
9
src/GalleryViewer/GalleryViewer.less
Normal file
@@ -0,0 +1,9 @@
|
||||
@import "../../less/Common/Constants";
|
||||
|
||||
.galleryContainer {
|
||||
padding: @DefaultSpace;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
font-family: @DataExplorerFont;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import { CosmosClient } from "../Common/CosmosClient";
|
||||
import { GalleryViewerComponent } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import "./GalleryViewer.less";
|
||||
import { GalleryViewerComponent } from "./GalleryViewerComponent";
|
||||
import { JunoUtils } from "../Utils/JunoUtils";
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
|
||||
const onInit = async () => {
|
||||
initializeIcons();
|
||||
const officialSamplesData = await JunoUtils.getOfficialSampleNotebooks(CosmosClient.authorizationToken());
|
||||
const officialSamplesData = await JunoUtils.getOfficialSampleNotebooks();
|
||||
const galleryViewerComponent = new GalleryViewerComponent({
|
||||
officialSamplesData: officialSamplesData,
|
||||
likedNotebookData: undefined,
|
||||
|
||||
207
src/GalleryViewer/GalleryViewerComponent.tsx
Normal file
207
src/GalleryViewer/GalleryViewerComponent.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Gallery Viewer
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { GalleryCardComponent } from "./Cards/GalleryCardComponent";
|
||||
import { Stack, IStackTokens } from "office-ui-fabric-react";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import { JunoUtils } from "../Utils/JunoUtils";
|
||||
import { CosmosClient } from "../Common/CosmosClient";
|
||||
import { config } from "../Config";
|
||||
import path from "path";
|
||||
import { SessionStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import "./GalleryViewer.less";
|
||||
|
||||
interface GalleryCardsComponentProps {
|
||||
data: DataModels.GitHubInfoJunoResponse[];
|
||||
onClick: (url: string, notebookMetadata: DataModels.NotebookMetadata) => Promise<void>;
|
||||
}
|
||||
|
||||
class GalleryCardsComponent extends React.Component<GalleryCardsComponentProps> {
|
||||
private sectionStackTokens: IStackTokens = { childrenGap: 30 };
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack horizontal wrap tokens={this.sectionStackTokens}>
|
||||
{this.props.data.map((githubInfo: DataModels.GitHubInfoJunoResponse, index: any) => {
|
||||
const name = githubInfo.name;
|
||||
const url = githubInfo.downloadUrl;
|
||||
const notebookMetadata = githubInfo.metadata;
|
||||
|
||||
return (
|
||||
name !== ".gitignore" &&
|
||||
url && (
|
||||
<GalleryCardComponent
|
||||
key={url}
|
||||
name={name}
|
||||
url={url}
|
||||
notebookMetadata={notebookMetadata}
|
||||
onClick={() => this.props.onClick(url, notebookMetadata)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const TabPanel = (props: any) => (
|
||||
<Typography
|
||||
component="div"
|
||||
role="tabpanel"
|
||||
hidden={props.value !== props.index}
|
||||
id={`full-width-tabpanel-${props.index}`}
|
||||
aria-labelledby={`full-width-tab-${props.index}`}
|
||||
>
|
||||
{props.value === props.index && <Box p={2}>{props.children}</Box>}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
const a11yProps = (index: number) => {
|
||||
return {
|
||||
id: `full-width-tab-${index}`,
|
||||
"aria-controls": `full-width-tabpanel-${index}`
|
||||
};
|
||||
};
|
||||
|
||||
interface FullWidthTabsProps {
|
||||
officialSamplesContent: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebooksContent: DataModels.GitHubInfoJunoResponse[];
|
||||
onClick: (url: string, notebookMetadata: DataModels.NotebookMetadata) => Promise<void>;
|
||||
}
|
||||
|
||||
const FullWidthTabs = (props: FullWidthTabsProps) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = ({}, newValue: any) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar position="static" color="transparent" style={{ background: "transparent", boxShadow: "none" }}>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="gallery tabs"
|
||||
>
|
||||
<Tab label="Official Samples" {...a11yProps(0)} />
|
||||
<Tab label="Liked Notebooks" {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<TabPanel value={value} index={0}>
|
||||
<GalleryCardsComponent data={props.officialSamplesContent} onClick={props.onClick} />
|
||||
</TabPanel>
|
||||
<TabPanel value={value} index={1}>
|
||||
<GalleryCardsComponent data={props.likedNotebooksContent} onClick={props.onClick} />
|
||||
</TabPanel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface GalleryViewerContainerComponentProps {
|
||||
container: ViewModels.Explorer;
|
||||
}
|
||||
|
||||
export interface GalleryViewerContainerComponentState {
|
||||
officialSamplesData: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebooksData: DataModels.LikedNotebooksJunoResponse;
|
||||
}
|
||||
|
||||
export class GalleryViewerContainerComponent extends React.Component<
|
||||
GalleryViewerContainerComponentProps,
|
||||
GalleryViewerContainerComponentState
|
||||
> {
|
||||
constructor(props: GalleryViewerContainerComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
officialSamplesData: undefined,
|
||||
likedNotebooksData: undefined
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
JunoUtils.getOfficialSampleNotebooks().then((data1: DataModels.GitHubInfoJunoResponse[]) => {
|
||||
const officialSamplesData = data1;
|
||||
|
||||
JunoUtils.getLikedNotebooks(CosmosClient.authorizationToken()).then(
|
||||
(data2: DataModels.LikedNotebooksJunoResponse) => {
|
||||
const likedNotebooksData = data2;
|
||||
|
||||
this.setState({
|
||||
officialSamplesData: officialSamplesData,
|
||||
likedNotebooksData: likedNotebooksData
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return this.state.officialSamplesData && this.state.likedNotebooksData ? (
|
||||
<GalleryViewerComponent
|
||||
container={this.props.container}
|
||||
officialSamplesData={this.state.officialSamplesData}
|
||||
likedNotebookData={this.state.likedNotebooksData}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface GalleryViewerComponentProps {
|
||||
container: ViewModels.Explorer;
|
||||
officialSamplesData: DataModels.GitHubInfoJunoResponse[];
|
||||
likedNotebookData: DataModels.LikedNotebooksJunoResponse;
|
||||
}
|
||||
|
||||
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps> {
|
||||
private authorizationToken = CosmosClient.authorizationToken();
|
||||
|
||||
public render(): JSX.Element {
|
||||
return this.props.container ? (
|
||||
<div className="galleryContainer">
|
||||
<FullWidthTabs
|
||||
officialSamplesContent={this.props.officialSamplesData}
|
||||
likedNotebooksContent={this.props.likedNotebookData.likedNotebooksContent}
|
||||
onClick={this.openNotebookViewer}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="galleryContainer">
|
||||
<GalleryCardsComponent data={this.props.officialSamplesData} onClick={this.openNotebookViewer} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public getOfficialSamplesData(): DataModels.GitHubInfoJunoResponse[] {
|
||||
return this.props.officialSamplesData;
|
||||
}
|
||||
|
||||
public getLikedNotebookData(): DataModels.LikedNotebooksJunoResponse {
|
||||
return this.props.likedNotebookData;
|
||||
}
|
||||
|
||||
public openNotebookViewer = async (url: string, notebookMetadata: DataModels.NotebookMetadata) => {
|
||||
if (!this.props.container) {
|
||||
SessionStorageUtility.setEntryString(
|
||||
StorageKey.NotebookMetadata,
|
||||
notebookMetadata ? JSON.stringify(notebookMetadata) : null
|
||||
);
|
||||
SessionStorageUtility.setEntryString(StorageKey.NotebookName, path.basename(url));
|
||||
window.open(`${config.hostedExplorerURL}notebookViewer.html?notebookurl=${url}`, "_blank");
|
||||
} else {
|
||||
this.props.container.openNotebookViewer(url, notebookMetadata);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,110 +1,92 @@
|
||||
import ko from "knockout";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import { GitHubClient, IGitHubFile } from "./GitHubClient";
|
||||
import { SamplesRepo, SamplesBranch, SamplesContentsQueryResponse } from "../Explorer/Notebook/NotebookSamples";
|
||||
import { GitHubClient, IGitHubBranch, IGitHubRepo } from "./GitHubClient";
|
||||
|
||||
const invalidTokenCallback = jest.fn();
|
||||
// Use a dummy token to get around API rate limit (something which doesn't affect the API quota for AZURESAMPLESCOSMOSDBPAT in Config.ts)
|
||||
const gitHubClient = new GitHubClient("cd1906b9534362fab6ce45d6db6c76b59e55bc50", invalidTokenCallback);
|
||||
|
||||
const validateGitHubFile = (file: IGitHubFile) => {
|
||||
expect(file.branch).toEqual(SamplesBranch);
|
||||
expect(file.commit).toBeDefined();
|
||||
expect(file.name).toBeDefined();
|
||||
expect(file.path).toBeDefined();
|
||||
expect(file.repo).toEqual(SamplesRepo);
|
||||
expect(file.type).toBeDefined();
|
||||
|
||||
switch (file.type) {
|
||||
case "blob":
|
||||
expect(file.sha).toBeDefined();
|
||||
expect(file.size).toBeDefined();
|
||||
break;
|
||||
|
||||
case "tree":
|
||||
expect(file.sha).toBeUndefined();
|
||||
expect(file.size).toBeUndefined();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported github file type: ${file.type}`);
|
||||
}
|
||||
// Use a dummy token to get around API rate limit (same as AZURESAMPLESCOSMOSDBPAT in webpack.config.js)
|
||||
const gitHubClient = new GitHubClient("99e38770e29b4a61d7c49f188780504efd35cc86", invalidTokenCallback);
|
||||
const samplesRepo: IGitHubRepo = {
|
||||
name: "cosmos-notebooks",
|
||||
owner: {
|
||||
login: "Azure-Samples"
|
||||
},
|
||||
private: false
|
||||
};
|
||||
const samplesBranch: IGitHubBranch = {
|
||||
name: "master"
|
||||
};
|
||||
const sampleFilePath = ".gitignore";
|
||||
const sampleDirPath = ".github";
|
||||
|
||||
describe("GitHubClient", () => {
|
||||
describe.skip("GitHubClient", () => {
|
||||
it("getRepoAsync returns valid repo", async () => {
|
||||
const response = await gitHubClient.getRepoAsync(SamplesRepo.owner, SamplesRepo.name);
|
||||
expect(response).toEqual({
|
||||
status: HttpStatusCodes.OK,
|
||||
data: SamplesRepo
|
||||
});
|
||||
const response = await gitHubClient.getRepoAsync(samplesRepo.owner.login, samplesRepo.name);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data.name).toBe(samplesRepo.name);
|
||||
expect(response.data.owner.login).toBe(samplesRepo.owner.login);
|
||||
});
|
||||
|
||||
it("getReposAsync returns repos for authenticated user", async () => {
|
||||
const response = await gitHubClient.getReposAsync(1);
|
||||
const response = await gitHubClient.getReposAsync(1, 1);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data.length).toBe(1);
|
||||
expect(response.pageInfo).toBeDefined();
|
||||
});
|
||||
|
||||
it("getBranchesAsync returns branches for a repo", async () => {
|
||||
const response = await gitHubClient.getBranchesAsync(SamplesRepo.owner, SamplesRepo.name, 1);
|
||||
const response = await gitHubClient.getBranchesAsync(samplesRepo.owner.login, samplesRepo.name, 1, 1);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toEqual([SamplesBranch]);
|
||||
expect(response.pageInfo).toBeDefined();
|
||||
expect(response.data.length).toBe(1);
|
||||
});
|
||||
|
||||
it("getContentsAsync returns files in the repo", async () => {
|
||||
const response = await gitHubClient.getContentsAsync(SamplesRepo.owner, SamplesRepo.name, SamplesBranch.name);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
const data = response.data as IGitHubFile[];
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
data.forEach(content => validateGitHubFile(content));
|
||||
});
|
||||
|
||||
it("getContentsAsync returns files in a dir", async () => {
|
||||
const samplesDir = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "tree");
|
||||
const response = await gitHubClient.getContentsAsync(
|
||||
SamplesRepo.owner,
|
||||
SamplesRepo.name,
|
||||
SamplesBranch.name,
|
||||
samplesDir.name
|
||||
it("getCommitsAsync returns commits for a file", async () => {
|
||||
const response = await gitHubClient.getCommitsAsync(
|
||||
samplesRepo.owner.login,
|
||||
samplesRepo.name,
|
||||
samplesBranch.name,
|
||||
sampleFilePath,
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
const data = response.data as IGitHubFile[];
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
data.forEach(content => validateGitHubFile(content));
|
||||
expect(response.data.length).toBe(1);
|
||||
});
|
||||
|
||||
it("getContentsAsync returns a file", async () => {
|
||||
const samplesFile = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "blob");
|
||||
const response = await gitHubClient.getContentsAsync(
|
||||
SamplesRepo.owner,
|
||||
SamplesRepo.name,
|
||||
SamplesBranch.name,
|
||||
samplesFile.name
|
||||
it("getDirContentsAsync returns files in the repo", async () => {
|
||||
const response = await gitHubClient.getDirContentsAsync(
|
||||
samplesRepo.owner.login,
|
||||
samplesRepo.name,
|
||||
samplesBranch.name,
|
||||
""
|
||||
);
|
||||
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
const file = response.data as IGitHubFile;
|
||||
expect(file.type).toBe("blob");
|
||||
validateGitHubFile(file);
|
||||
expect(file.content).toBeUndefined();
|
||||
expect(response.data.length).toBeGreaterThan(0);
|
||||
expect(response.data[0].repo).toEqual(samplesRepo);
|
||||
expect(response.data[0].branch).toEqual(samplesBranch);
|
||||
});
|
||||
|
||||
it("getBlobAsync returns file content", async () => {
|
||||
const samplesFile = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "blob");
|
||||
const response = await gitHubClient.getBlobAsync(SamplesRepo.owner, SamplesRepo.name, samplesFile.object.oid);
|
||||
|
||||
it("getDirContentsAsync returns files in a dir", async () => {
|
||||
const response = await gitHubClient.getDirContentsAsync(
|
||||
samplesRepo.owner.login,
|
||||
samplesRepo.name,
|
||||
samplesBranch.name,
|
||||
sampleDirPath
|
||||
);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(typeof response.data).toBe("string");
|
||||
expect(response.data.length).toBeGreaterThan(0);
|
||||
expect(response.data[0].repo).toEqual(samplesRepo);
|
||||
expect(response.data[0].branch).toEqual(samplesBranch);
|
||||
});
|
||||
|
||||
it("getFileContentsAsync returns a file", async () => {
|
||||
const response = await gitHubClient.getFileContentsAsync(
|
||||
samplesRepo.owner.login,
|
||||
samplesRepo.name,
|
||||
samplesBranch.name,
|
||||
sampleFilePath
|
||||
);
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(response.data.path).toBe(sampleFilePath);
|
||||
expect(response.data.repo).toEqual(samplesRepo);
|
||||
expect(response.data.branch).toEqual(samplesBranch);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,228 +1,163 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { RequestHeaders } from "@octokit/types";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import { Logger } from "../Common/Logger";
|
||||
import UrlUtility from "../Common/UrlUtility";
|
||||
import { isSamplesCall, SamplesContentsQueryResponse } from "../Explorer/Notebook/NotebookSamples";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
|
||||
export interface IGitHubPageInfo {
|
||||
endCursor: string;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export interface IGitHubResponse<T> {
|
||||
status: number;
|
||||
data: T;
|
||||
pageInfo?: IGitHubPageInfo;
|
||||
}
|
||||
|
||||
export interface IGitHubRepo {
|
||||
name: string;
|
||||
owner: string;
|
||||
private: boolean;
|
||||
children?: IGitHubFile[];
|
||||
}
|
||||
|
||||
export interface IGitHubFile {
|
||||
type: "blob" | "tree";
|
||||
size?: number;
|
||||
name: string;
|
||||
path: string;
|
||||
content?: string;
|
||||
sha?: string;
|
||||
children?: IGitHubFile[];
|
||||
repo: IGitHubRepo;
|
||||
branch: IGitHubBranch;
|
||||
commit: IGitHubCommit;
|
||||
}
|
||||
|
||||
export interface IGitHubCommit {
|
||||
sha: string;
|
||||
message: string;
|
||||
commitDate: string;
|
||||
}
|
||||
|
||||
export interface IGitHubBranch {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// graphql schema
|
||||
interface Collection<T> {
|
||||
pageInfo?: PageInfo;
|
||||
nodes: T[];
|
||||
}
|
||||
|
||||
interface Repository {
|
||||
isPrivate: boolean;
|
||||
// API properties
|
||||
name: string;
|
||||
owner: {
|
||||
login: string;
|
||||
};
|
||||
private: boolean;
|
||||
|
||||
// Custom properties
|
||||
children?: IGitHubFile[];
|
||||
}
|
||||
|
||||
interface Ref {
|
||||
export interface IGitHubFile {
|
||||
// API properties
|
||||
type: "file" | "dir" | "symlink" | "submodule";
|
||||
encoding?: string;
|
||||
size: number;
|
||||
name: string;
|
||||
path: string;
|
||||
content?: string;
|
||||
sha: string;
|
||||
|
||||
// Custom properties
|
||||
children?: IGitHubFile[];
|
||||
repo?: IGitHubRepo;
|
||||
branch?: IGitHubBranch;
|
||||
}
|
||||
|
||||
interface History {
|
||||
history: Collection<Commit>;
|
||||
}
|
||||
|
||||
interface Commit {
|
||||
export interface IGitHubCommit {
|
||||
// API properties
|
||||
sha: string;
|
||||
message: string;
|
||||
committer: {
|
||||
date: string;
|
||||
};
|
||||
message: string;
|
||||
oid: string;
|
||||
}
|
||||
|
||||
interface Tree extends Blob {
|
||||
entries: TreeEntry[];
|
||||
}
|
||||
|
||||
interface TreeEntry {
|
||||
export interface IGitHubBranch {
|
||||
// API properties
|
||||
name: string;
|
||||
type: string;
|
||||
object: Blob;
|
||||
}
|
||||
|
||||
interface Blob {
|
||||
byteSize?: number;
|
||||
oid?: string;
|
||||
export interface IGitHubUser {
|
||||
// API properties
|
||||
login: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PageInfo {
|
||||
endCursor: string;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
// graphql queries and types
|
||||
const repositoryQuery = `query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
owner {
|
||||
login
|
||||
}
|
||||
name
|
||||
isPrivate
|
||||
}
|
||||
}`;
|
||||
type RepositoryQueryParams = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
};
|
||||
type RepositoryQueryResponse = {
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
const repositoriesQuery = `query($pageSize: Int!, $endCursor: String) {
|
||||
viewer {
|
||||
repositories(first: $pageSize, after: $endCursor) {
|
||||
pageInfo {
|
||||
endCursor,
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
owner {
|
||||
login
|
||||
}
|
||||
name
|
||||
isPrivate
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
type RepositoriesQueryParams = {
|
||||
pageSize: number;
|
||||
endCursor?: string;
|
||||
};
|
||||
type RepositoriesQueryResponse = {
|
||||
viewer: {
|
||||
repositories: Collection<Repository>;
|
||||
};
|
||||
};
|
||||
|
||||
const branchesQuery = `query($owner: String!, $repo: String!, $refPrefix: String!, $pageSize: Int!, $endCursor: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
refs(refPrefix: $refPrefix, first: $pageSize, after: $endCursor) {
|
||||
pageInfo {
|
||||
endCursor,
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
type BranchesQueryParams = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
refPrefix: string;
|
||||
pageSize: number;
|
||||
endCursor?: string;
|
||||
};
|
||||
type BranchesQueryResponse = {
|
||||
repository: {
|
||||
refs: Collection<Ref>;
|
||||
};
|
||||
};
|
||||
|
||||
const contentsQuery = `query($owner: String!, $repo: String!, $ref: String!, $path: String, $objectExpression: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
owner {
|
||||
login
|
||||
}
|
||||
name
|
||||
isPrivate
|
||||
ref(qualifiedName: $ref) {
|
||||
name
|
||||
target {
|
||||
... on Commit {
|
||||
history(first: 1, path: $path) {
|
||||
nodes {
|
||||
oid
|
||||
message
|
||||
committer {
|
||||
date
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
object(expression: $objectExpression) {
|
||||
... on Blob {
|
||||
oid
|
||||
byteSize
|
||||
}
|
||||
... on Tree {
|
||||
entries {
|
||||
name
|
||||
type
|
||||
object {
|
||||
... on Blob {
|
||||
oid
|
||||
byteSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
type ContentsQueryParams = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
ref: string;
|
||||
path?: string;
|
||||
objectExpression: string;
|
||||
};
|
||||
type ContentsQueryResponse = {
|
||||
repository: Repository & { ref: Ref & { target: History } } & { object: Tree };
|
||||
};
|
||||
|
||||
export class GitHubClient {
|
||||
private static readonly SelfErrorCode = 599;
|
||||
private static readonly gitHubApiEndpoint = "https://api.github.com";
|
||||
|
||||
private static readonly samplesRepo: IGitHubRepo = {
|
||||
name: "cosmos-notebooks",
|
||||
private: false,
|
||||
owner: {
|
||||
login: "Azure-Samples"
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly samplesBranch: IGitHubBranch = {
|
||||
name: "master"
|
||||
};
|
||||
|
||||
private static readonly samplesTopCommit: IGitHubCommit = {
|
||||
sha: "41b964f442b638097a75a3f3b6a6451db05a12bf",
|
||||
committer: {
|
||||
date: "2020-05-19T05:03:30Z"
|
||||
},
|
||||
message: "Fixing formatting"
|
||||
};
|
||||
|
||||
private static readonly samplesFiles: IGitHubFile[] = [
|
||||
{
|
||||
name: ".github",
|
||||
path: ".github",
|
||||
sha: "5e6794a8177a0c07a8719f6e1d7b41cce6f92e1e",
|
||||
size: 0,
|
||||
type: "dir"
|
||||
},
|
||||
{
|
||||
name: ".gitignore",
|
||||
path: ".gitignore",
|
||||
sha: "3e759b75bf455ac809d0987d369aab89137b5689",
|
||||
size: 5582,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "1. GettingStarted.ipynb",
|
||||
path: "1. GettingStarted.ipynb",
|
||||
sha: "0732ff5366e4aefdc4c378c61cbd968664f0acec",
|
||||
size: 3933,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "2. Visualization.ipynb",
|
||||
path: "2. Visualization.ipynb",
|
||||
sha: "f480134ac4adf2f50ce5fe66836c6966749d3ca1",
|
||||
size: 814261,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "3. RequestUnits.ipynb",
|
||||
path: "3. RequestUnits.ipynb",
|
||||
sha: "252b79a4adc81e9f2ffde453231b695d75e270e8",
|
||||
size: 9490,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "4. Indexing.ipynb",
|
||||
path: "4. Indexing.ipynb",
|
||||
sha: "e10dd67bd1c55c345226769e4f80e43659ef9cd5",
|
||||
size: 10394,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "5. StoredProcedures.ipynb",
|
||||
path: "5. StoredProcedures.ipynb",
|
||||
sha: "949941949920de4d2d111149e2182e9657cc8134",
|
||||
size: 11818,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "6. GlobalDistribution.ipynb",
|
||||
path: "6. GlobalDistribution.ipynb",
|
||||
sha: "b91c31dacacbc9e35750d9054063dda4a5309f3b",
|
||||
size: 11375,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "7. IoTAnomalyDetection.ipynb",
|
||||
path: "7. IoTAnomalyDetection.ipynb",
|
||||
sha: "82057ae52a67721a5966e2361317f5dfbd0ee595",
|
||||
size: 377939,
|
||||
type: "file"
|
||||
},
|
||||
{
|
||||
name: "All_API_quickstarts",
|
||||
path: "All_API_quickstarts",
|
||||
sha: "07054293e6c8fc00771fccd0cde207f5c8053978",
|
||||
size: 0,
|
||||
type: "dir"
|
||||
},
|
||||
{
|
||||
name: "CSharp_quickstarts",
|
||||
path: "CSharp_quickstarts",
|
||||
sha: "10e7f5704e6b56a40cac74bc39f15b7708954f52",
|
||||
size: 0,
|
||||
type: "dir"
|
||||
}
|
||||
];
|
||||
|
||||
private ocktokit: Octokit;
|
||||
|
||||
constructor(token: string, private errorCallback: (error: any) => void) {
|
||||
@@ -234,136 +169,167 @@ export class GitHubClient {
|
||||
}
|
||||
|
||||
public async getRepoAsync(owner: string, repo: string): Promise<IGitHubResponse<IGitHubRepo>> {
|
||||
try {
|
||||
const response = (await this.ocktokit.graphql(repositoryQuery, {
|
||||
owner,
|
||||
repo
|
||||
} as RepositoryQueryParams)) as RepositoryQueryResponse;
|
||||
|
||||
if (GitHubClient.isSamplesCall(owner, repo)) {
|
||||
return {
|
||||
status: HttpStatusCodes.OK,
|
||||
data: GitHubClient.toGitHubRepo(response.repository)
|
||||
};
|
||||
} catch (error) {
|
||||
GitHubClient.log(Logger.logError, `GitHubClient.getRepoAsync failed: ${error}`);
|
||||
return {
|
||||
status: GitHubClient.SelfErrorCode,
|
||||
data: undefined
|
||||
data: GitHubClient.samplesRepo
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.ocktokit.repos.get({
|
||||
owner,
|
||||
repo,
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
let data: IGitHubRepo;
|
||||
if (response.data) {
|
||||
data = GitHubClient.toGitHubRepo(response.data);
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async getReposAsync(pageSize: number, endCursor?: string): Promise<IGitHubResponse<IGitHubRepo[]>> {
|
||||
try {
|
||||
const response = (await this.ocktokit.graphql(repositoriesQuery, {
|
||||
pageSize,
|
||||
endCursor
|
||||
} as RepositoriesQueryParams)) as RepositoriesQueryResponse;
|
||||
public async getReposAsync(page: number, perPage: number): Promise<IGitHubResponse<IGitHubRepo[]>> {
|
||||
const response = await this.ocktokit.repos.listForAuthenticatedUser({
|
||||
page,
|
||||
per_page: perPage,
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
return {
|
||||
status: HttpStatusCodes.OK,
|
||||
data: response.viewer.repositories.nodes.map(repo => GitHubClient.toGitHubRepo(repo)),
|
||||
pageInfo: GitHubClient.toGitHubPageInfo(response.viewer.repositories.pageInfo)
|
||||
};
|
||||
} catch (error) {
|
||||
GitHubClient.log(Logger.logError, `GitHubClient.getReposAsync failed: ${error}`);
|
||||
return {
|
||||
status: GitHubClient.SelfErrorCode,
|
||||
data: undefined
|
||||
};
|
||||
let data: IGitHubRepo[];
|
||||
if (response.data) {
|
||||
data = [];
|
||||
response.data?.forEach((element: any) => data.push(GitHubClient.toGitHubRepo(element)));
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async getBranchesAsync(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pageSize: number,
|
||||
endCursor?: string
|
||||
page: number,
|
||||
perPage: number
|
||||
): Promise<IGitHubResponse<IGitHubBranch[]>> {
|
||||
try {
|
||||
const response = (await this.ocktokit.graphql(branchesQuery, {
|
||||
owner,
|
||||
repo,
|
||||
refPrefix: "refs/heads/",
|
||||
pageSize,
|
||||
endCursor
|
||||
} as BranchesQueryParams)) as BranchesQueryResponse;
|
||||
const response = await this.ocktokit.repos.listBranches({
|
||||
owner,
|
||||
repo,
|
||||
page,
|
||||
per_page: perPage,
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
let data: IGitHubBranch[];
|
||||
if (response.data) {
|
||||
data = [];
|
||||
response.data?.forEach(element => data.push(GitHubClient.toGitHubBranch(element)));
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async getCommitsAsync(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
path: string,
|
||||
page: number,
|
||||
perPage: number
|
||||
): Promise<IGitHubResponse<IGitHubCommit[]>> {
|
||||
if (GitHubClient.isSamplesCall(owner, repo, branch) && path === "" && page === 1 && perPage === 1) {
|
||||
return {
|
||||
status: HttpStatusCodes.OK,
|
||||
data: response.repository.refs.nodes.map(ref => GitHubClient.toGitHubBranch(ref)),
|
||||
pageInfo: GitHubClient.toGitHubPageInfo(response.repository.refs.pageInfo)
|
||||
};
|
||||
} catch (error) {
|
||||
GitHubClient.log(Logger.logError, `GitHubClient.getBranchesAsync failed: ${error}`);
|
||||
return {
|
||||
status: GitHubClient.SelfErrorCode,
|
||||
data: undefined
|
||||
data: [GitHubClient.samplesTopCommit]
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.ocktokit.repos.listCommits({
|
||||
owner,
|
||||
repo,
|
||||
sha: branch,
|
||||
path,
|
||||
page,
|
||||
per_page: perPage,
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
let data: IGitHubCommit[];
|
||||
if (response.data) {
|
||||
data = [];
|
||||
response.data?.forEach(element =>
|
||||
data.push(GitHubClient.toGitHubCommit({ ...element.commit, sha: element.sha }))
|
||||
);
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async getDirContentsAsync(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
path: string
|
||||
): Promise<IGitHubResponse<IGitHubFile[]>> {
|
||||
return (await this.getContentsAsync(owner, repo, branch, path)) as IGitHubResponse<IGitHubFile[]>;
|
||||
}
|
||||
|
||||
public async getFileContentsAsync(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
path: string
|
||||
): Promise<IGitHubResponse<IGitHubFile>> {
|
||||
return (await this.getContentsAsync(owner, repo, branch, path)) as IGitHubResponse<IGitHubFile>;
|
||||
}
|
||||
|
||||
public async getContentsAsync(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
path?: string
|
||||
path: string
|
||||
): Promise<IGitHubResponse<IGitHubFile | IGitHubFile[]>> {
|
||||
try {
|
||||
let response: ContentsQueryResponse;
|
||||
if (isSamplesCall(owner, repo, branch) && !path) {
|
||||
response = SamplesContentsQueryResponse;
|
||||
} else {
|
||||
response = (await this.ocktokit.graphql(contentsQuery, {
|
||||
owner,
|
||||
repo,
|
||||
ref: `refs/heads/${branch}`,
|
||||
path: path || undefined,
|
||||
objectExpression: `refs/heads/${branch}:${path || ""}`
|
||||
} as ContentsQueryParams)) as ContentsQueryResponse;
|
||||
}
|
||||
|
||||
let data: IGitHubFile | IGitHubFile[];
|
||||
const entries = response.repository.object.entries;
|
||||
const gitHubRepo = GitHubClient.toGitHubRepo(response.repository);
|
||||
const gitHubBranch = GitHubClient.toGitHubBranch(response.repository.ref);
|
||||
const gitHubCommit = GitHubClient.toGitHubCommit(response.repository.ref.target.history.nodes[0]);
|
||||
|
||||
if (Array.isArray(entries)) {
|
||||
data = entries.map(entry =>
|
||||
GitHubClient.toGitHubFile(
|
||||
entry,
|
||||
(path && UrlUtility.createUri(path, entry.name)) || entry.name,
|
||||
gitHubRepo,
|
||||
gitHubBranch,
|
||||
gitHubCommit
|
||||
)
|
||||
);
|
||||
} else {
|
||||
data = GitHubClient.toGitHubFile(
|
||||
{
|
||||
name: NotebookUtil.getName(path),
|
||||
type: "blob",
|
||||
object: response.repository.object
|
||||
},
|
||||
path,
|
||||
gitHubRepo,
|
||||
gitHubBranch,
|
||||
gitHubCommit
|
||||
);
|
||||
}
|
||||
|
||||
if (GitHubClient.isSamplesCall(owner, repo, branch) && path === "") {
|
||||
return {
|
||||
status: HttpStatusCodes.OK,
|
||||
data
|
||||
};
|
||||
} catch (error) {
|
||||
GitHubClient.log(Logger.logError, `GitHubClient.getContentsAsync failed: ${error}`);
|
||||
return {
|
||||
status: GitHubClient.SelfErrorCode,
|
||||
data: undefined
|
||||
data: GitHubClient.samplesFiles.map(file =>
|
||||
GitHubClient.toGitHubFile(file, GitHubClient.samplesRepo, GitHubClient.samplesBranch)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.ocktokit.repos.getContents({
|
||||
owner,
|
||||
repo,
|
||||
path,
|
||||
ref: branch,
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
let data: IGitHubFile | IGitHubFile[];
|
||||
if (response.data) {
|
||||
const repoResponse = await this.getRepoAsync(owner, repo);
|
||||
if (repoResponse.data) {
|
||||
const fileRepo: IGitHubRepo = GitHubClient.toGitHubRepo(repoResponse.data);
|
||||
const fileBranch: IGitHubBranch = { name: branch };
|
||||
|
||||
if (Array.isArray(response.data)) {
|
||||
const contents: IGitHubFile[] = [];
|
||||
response.data.forEach((element: any) =>
|
||||
contents.push(GitHubClient.toGitHubFile(element, fileRepo, fileBranch))
|
||||
);
|
||||
data = contents;
|
||||
} else {
|
||||
data = GitHubClient.toGitHubFile(
|
||||
{ ...response.data, type: response.data.type as "file" | "dir" | "symlink" | "submodule" },
|
||||
fileRepo,
|
||||
fileBranch
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async createOrUpdateFileAsync(
|
||||
@@ -406,9 +372,7 @@ export class GitHubClient {
|
||||
owner,
|
||||
repo,
|
||||
ref,
|
||||
headers: {
|
||||
"If-None-Match": "" // disable 60s cache
|
||||
}
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
const currentTree = await this.ocktokit.git.getTree({
|
||||
@@ -416,9 +380,7 @@ export class GitHubClient {
|
||||
repo,
|
||||
tree_sha: currentRef.data.object.sha,
|
||||
recursive: "1",
|
||||
headers: {
|
||||
"If-None-Match": "" // disable 60s cache
|
||||
}
|
||||
headers: GitHubClient.getDisableCacheHeaders()
|
||||
});
|
||||
|
||||
// API infers tree from paths so we need to filter them out
|
||||
@@ -463,7 +425,7 @@ export class GitHubClient {
|
||||
|
||||
public async deleteFileAsync(file: IGitHubFile, message: string): Promise<IGitHubResponse<IGitHubCommit>> {
|
||||
const response = await this.ocktokit.repos.deleteFile({
|
||||
owner: file.repo.owner,
|
||||
owner: file.repo.owner.login,
|
||||
repo: file.repo.name,
|
||||
path: file.path,
|
||||
message,
|
||||
@@ -479,31 +441,10 @@ export class GitHubClient {
|
||||
return { status: response.status, data };
|
||||
}
|
||||
|
||||
public async getBlobAsync(owner: string, repo: string, sha: string): Promise<IGitHubResponse<string>> {
|
||||
const response = await this.ocktokit.git.getBlob({
|
||||
owner,
|
||||
repo,
|
||||
file_sha: sha,
|
||||
mediaType: {
|
||||
format: "raw"
|
||||
},
|
||||
headers: {
|
||||
"If-None-Match": "" // disable 60s cache
|
||||
}
|
||||
});
|
||||
|
||||
return { status: response.status, data: <string>(<unknown>response.data) };
|
||||
}
|
||||
|
||||
private async initOctokit(token: string) {
|
||||
private initOctokit(token: string) {
|
||||
this.ocktokit = new Octokit({
|
||||
auth: token,
|
||||
log: {
|
||||
debug: () => {},
|
||||
info: (message?: any) => GitHubClient.log(Logger.logInfo, message),
|
||||
warn: (message?: any) => GitHubClient.log(Logger.logWarning, message),
|
||||
error: (message?: any) => GitHubClient.log(Logger.logError, message)
|
||||
}
|
||||
baseUrl: GitHubClient.gitHubApiEndpoint
|
||||
});
|
||||
|
||||
this.ocktokit.hook.error("request", error => {
|
||||
@@ -512,69 +453,53 @@ export class GitHubClient {
|
||||
});
|
||||
}
|
||||
|
||||
private static log(logger: (message: string, area: string) => void, message?: any) {
|
||||
if (message) {
|
||||
message = typeof message === "string" ? message : JSON.stringify(message);
|
||||
logger(message, "GitHubClient.Octokit");
|
||||
}
|
||||
}
|
||||
|
||||
private static toGitHubRepo(object: Repository): IGitHubRepo {
|
||||
private static getDisableCacheHeaders(): RequestHeaders {
|
||||
return {
|
||||
owner: object.owner.login,
|
||||
name: object.name,
|
||||
private: object.isPrivate
|
||||
"If-None-Match": ""
|
||||
};
|
||||
}
|
||||
|
||||
private static toGitHubBranch(object: Ref): IGitHubBranch {
|
||||
private static toGitHubRepo(element: IGitHubRepo): IGitHubRepo {
|
||||
return {
|
||||
name: object.name
|
||||
name: element.name,
|
||||
owner: {
|
||||
login: element.owner.login
|
||||
},
|
||||
private: element.private
|
||||
};
|
||||
}
|
||||
|
||||
private static toGitHubCommit(object: {
|
||||
message: string;
|
||||
committer: {
|
||||
date: string;
|
||||
};
|
||||
sha?: string;
|
||||
oid?: string;
|
||||
}): IGitHubCommit {
|
||||
private static toGitHubBranch(element: IGitHubBranch): IGitHubBranch {
|
||||
return {
|
||||
sha: object.sha || object.oid,
|
||||
message: object.message,
|
||||
commitDate: object.committer.date
|
||||
name: element.name
|
||||
};
|
||||
}
|
||||
|
||||
private static toGitHubPageInfo(object: PageInfo): IGitHubPageInfo {
|
||||
private static toGitHubCommit(element: IGitHubCommit): IGitHubCommit {
|
||||
return {
|
||||
endCursor: object.endCursor,
|
||||
hasNextPage: object.hasNextPage
|
||||
sha: element.sha,
|
||||
message: element.message,
|
||||
committer: {
|
||||
date: element.committer.date
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static toGitHubFile(
|
||||
entry: TreeEntry,
|
||||
path: string,
|
||||
repo: IGitHubRepo,
|
||||
branch: IGitHubBranch,
|
||||
commit: IGitHubCommit
|
||||
): IGitHubFile {
|
||||
if (entry.type !== "blob" && entry.type !== "tree") {
|
||||
throw new Error(`Unsupported file type: ${entry.type}`);
|
||||
}
|
||||
|
||||
private static toGitHubFile(element: IGitHubFile, repo: IGitHubRepo, branch: IGitHubBranch): IGitHubFile {
|
||||
return {
|
||||
type: entry.type,
|
||||
name: entry.name,
|
||||
path,
|
||||
type: element.type,
|
||||
encoding: element.encoding,
|
||||
size: element.size,
|
||||
name: element.name,
|
||||
path: element.path,
|
||||
content: element.content,
|
||||
sha: element.sha,
|
||||
repo,
|
||||
branch,
|
||||
commit,
|
||||
size: entry.object?.byteSize,
|
||||
sha: entry.object?.oid
|
||||
branch
|
||||
};
|
||||
}
|
||||
|
||||
private static isSamplesCall(owner: string, repo: string, branch?: string): boolean {
|
||||
return owner === "Azure-Samples" && repo === "cosmos-notebooks" && (!branch || branch === "master");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,27 @@ const gitHubContentProvider = new GitHubContentProvider({
|
||||
gitHubClient,
|
||||
promptForCommitMsg: () => Promise.resolve("commit msg")
|
||||
});
|
||||
const gitHubCommit: IGitHubCommit = {
|
||||
sha: "sha",
|
||||
message: "message",
|
||||
commitDate: "date"
|
||||
};
|
||||
const sampleFile: IGitHubFile = {
|
||||
type: "blob",
|
||||
type: "file",
|
||||
encoding: "encoding",
|
||||
size: 0,
|
||||
name: "name.ipynb",
|
||||
path: "dir/name.ipynb",
|
||||
content: fixture,
|
||||
content: btoa(fixture),
|
||||
sha: "sha",
|
||||
repo: {
|
||||
owner: "owner",
|
||||
owner: {
|
||||
login: "login"
|
||||
},
|
||||
name: "repo",
|
||||
private: false
|
||||
},
|
||||
branch: {
|
||||
name: "branch"
|
||||
},
|
||||
commit: gitHubCommit
|
||||
}
|
||||
};
|
||||
const sampleGitHubUri = GitHubUtils.toContentUri(
|
||||
sampleFile.repo.owner,
|
||||
sampleFile.repo.owner.login,
|
||||
sampleFile.repo.name,
|
||||
sampleFile.branch.name,
|
||||
sampleFile.path
|
||||
@@ -46,9 +43,16 @@ const sampleNotebookModel: IContent<"notebook"> = {
|
||||
created: "",
|
||||
last_modified: "date",
|
||||
mimetype: "application/x-ipynb+json",
|
||||
content: sampleFile.content ? JSON.parse(sampleFile.content) : null,
|
||||
content: sampleFile.content ? JSON.parse(atob(sampleFile.content)) : null,
|
||||
format: "json"
|
||||
};
|
||||
const gitHubCommit: IGitHubCommit = {
|
||||
sha: "sha",
|
||||
message: "message",
|
||||
committer: {
|
||||
date: "date"
|
||||
}
|
||||
};
|
||||
|
||||
describe("GitHubContentProvider remove", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
@@ -121,6 +125,9 @@ describe("GitHubContentProvider get", () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: sampleFile })
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "getCommitsAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: [gitHubCommit] })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.get(null, sampleGitHubUri, {}).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
@@ -169,6 +176,9 @@ describe("GitHubContentProvider update", () => {
|
||||
spyOn(GitHubClient.prototype, "renameFileAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "getCommitsAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: [gitHubCommit] })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.update(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
@@ -205,14 +215,18 @@ describe("GitHubContentProvider create", () => {
|
||||
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.Created, data: gitHubCommit })
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: sampleFile })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.create(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.Created);
|
||||
expect(gitHubClient.createOrUpdateFileAsync).toBeCalled();
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
expect(response.response.type).toEqual(sampleNotebookModel.type);
|
||||
expect(response.response.name).toBeDefined();
|
||||
expect(response.response.path).toBeDefined();
|
||||
expect(response.response.name).toEqual(sampleNotebookModel.name);
|
||||
expect(response.response.path).toEqual(sampleNotebookModel.path);
|
||||
expect(response.response.content).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AjaxResponse } from "rxjs/ajax";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import { Logger } from "../Common/Logger";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
import { GitHubClient, IGitHubFile, IGitHubResponse, IGitHubCommit, IGitHubBranch } from "./GitHubClient";
|
||||
import { GitHubClient, IGitHubFile, IGitHubResponse, IGitHubCommit } from "./GitHubClient";
|
||||
import { GitHubUtils } from "../Utils/GitHubUtils";
|
||||
import UrlUtility from "../Common/UrlUtility";
|
||||
|
||||
@@ -54,14 +54,23 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
throw new GitHubContentProviderError("Failed to get content", content.status);
|
||||
}
|
||||
|
||||
if (!Array.isArray(content.data) && !content.data.content && params.content !== 0) {
|
||||
const file = content.data;
|
||||
file.content = (
|
||||
await this.params.gitHubClient.getBlobAsync(file.repo.owner, file.repo.name, file.sha)
|
||||
).data;
|
||||
const contentInfo = GitHubUtils.fromContentUri(uri);
|
||||
const commitResponse = await this.params.gitHubClient.getCommitsAsync(
|
||||
contentInfo.owner,
|
||||
contentInfo.repo,
|
||||
contentInfo.branch,
|
||||
contentInfo.path,
|
||||
1,
|
||||
1
|
||||
);
|
||||
if (commitResponse.status !== HttpStatusCodes.OK) {
|
||||
throw new GitHubContentProviderError("Failed to get commit", commitResponse.status);
|
||||
}
|
||||
|
||||
return this.createSuccessAjaxResponse(HttpStatusCodes.OK, this.createContentModel(uri, content.data, params));
|
||||
return this.createSuccessAjaxResponse(
|
||||
HttpStatusCodes.OK,
|
||||
this.createContentModel(uri, content.data, commitResponse.data[0], params)
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "GitHubContentProvider/get", error.errno);
|
||||
return this.createErrorAjaxResponse(error);
|
||||
@@ -81,26 +90,26 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
const gitHubFile = content.data as IGitHubFile;
|
||||
const commitMsg = await this.validateContentAndGetCommitMsg(content, "Rename", "Rename");
|
||||
const newUri = model.path;
|
||||
const newPath = GitHubUtils.fromContentUri(newUri).path;
|
||||
const response = await this.params.gitHubClient.renameFileAsync(
|
||||
gitHubFile.repo.owner,
|
||||
gitHubFile.repo.owner.login,
|
||||
gitHubFile.repo.name,
|
||||
gitHubFile.branch.name,
|
||||
commitMsg,
|
||||
gitHubFile.path,
|
||||
newPath
|
||||
GitHubUtils.fromContentUri(newUri).path
|
||||
);
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new GitHubContentProviderError("Failed to rename", response.status);
|
||||
}
|
||||
|
||||
gitHubFile.commit = response.data;
|
||||
gitHubFile.path = newPath;
|
||||
gitHubFile.name = NotebookUtil.getName(gitHubFile.path);
|
||||
const updatedContentResponse = await this.getContent(model.path);
|
||||
if (updatedContentResponse.status !== HttpStatusCodes.OK) {
|
||||
throw new GitHubContentProviderError("Failed to get content after renaming", updatedContentResponse.status);
|
||||
}
|
||||
|
||||
return this.createSuccessAjaxResponse(
|
||||
HttpStatusCodes.OK,
|
||||
this.createContentModel(newUri, gitHubFile, { content: 0 })
|
||||
this.createContentModel(newUri, updatedContentResponse.data, response.data, { content: 0 })
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "GitHubContentProvider/update", error.errno);
|
||||
@@ -160,24 +169,14 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
}
|
||||
|
||||
const newUri = GitHubUtils.toContentUri(contentInfo.owner, contentInfo.repo, contentInfo.branch, path);
|
||||
const newGitHubFile: IGitHubFile = {
|
||||
type: "blob",
|
||||
name: NotebookUtil.getName(newUri),
|
||||
path,
|
||||
repo: {
|
||||
owner: contentInfo.owner,
|
||||
name: contentInfo.repo,
|
||||
private: undefined
|
||||
},
|
||||
branch: {
|
||||
name: contentInfo.branch
|
||||
},
|
||||
commit: response.data
|
||||
};
|
||||
const newContentResponse = await this.getContent(newUri);
|
||||
if (newContentResponse.status !== HttpStatusCodes.OK) {
|
||||
throw new GitHubContentProviderError("Failed to get content after creating", newContentResponse.status);
|
||||
}
|
||||
|
||||
return this.createSuccessAjaxResponse(
|
||||
HttpStatusCodes.Created,
|
||||
this.createContentModel(newUri, newGitHubFile, { content: 0 })
|
||||
this.createContentModel(newUri, newContentResponse.data, response.data, { content: 0 })
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "GitHubContentProvider/create", error.errno);
|
||||
@@ -210,7 +209,7 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
|
||||
const gitHubFile = content.data as IGitHubFile;
|
||||
const response = await this.params.gitHubClient.createOrUpdateFileAsync(
|
||||
gitHubFile.repo.owner,
|
||||
gitHubFile.repo.owner.login,
|
||||
gitHubFile.repo.name,
|
||||
gitHubFile.branch.name,
|
||||
gitHubFile.path,
|
||||
@@ -222,11 +221,14 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
throw new GitHubContentProviderError("Failed to update", response.status);
|
||||
}
|
||||
|
||||
gitHubFile.commit = response.data;
|
||||
const savedContentResponse = await this.getContent(uri);
|
||||
if (savedContentResponse.status !== HttpStatusCodes.OK) {
|
||||
throw new GitHubContentProviderError("Failed to get content after updating", savedContentResponse.status);
|
||||
}
|
||||
|
||||
return this.createSuccessAjaxResponse(
|
||||
HttpStatusCodes.OK,
|
||||
this.createContentModel(uri, gitHubFile, { content: 0 })
|
||||
this.createContentModel(uri, savedContentResponse.data, response.data, { content: 0 })
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "GitHubContentProvider/update", error.errno);
|
||||
@@ -281,7 +283,7 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
return commitMsg;
|
||||
}
|
||||
|
||||
private async getContent(uri: string): Promise<IGitHubResponse<IGitHubFile | IGitHubFile[]>> {
|
||||
private getContent(uri: string): Promise<IGitHubResponse<IGitHubFile | IGitHubFile[]>> {
|
||||
const contentInfo = GitHubUtils.fromContentUri(uri);
|
||||
if (contentInfo) {
|
||||
const { owner, repo, branch, path } = contentInfo;
|
||||
@@ -294,37 +296,43 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
private createContentModel(
|
||||
uri: string,
|
||||
content: IGitHubFile | IGitHubFile[],
|
||||
commit: IGitHubCommit,
|
||||
params: Partial<IGetParams>
|
||||
): IContent<FileType> {
|
||||
if (Array.isArray(content)) {
|
||||
return this.createDirectoryModel(uri, content);
|
||||
return this.createDirectoryModel(uri, content, commit);
|
||||
}
|
||||
|
||||
if (content.type === "tree") {
|
||||
return this.createDirectoryModel(uri, undefined);
|
||||
if (content.type !== "file") {
|
||||
return this.createDirectoryModel(uri, undefined, commit);
|
||||
}
|
||||
|
||||
if (NotebookUtil.isNotebookFile(uri)) {
|
||||
return this.createNotebookModel(content, params);
|
||||
return this.createNotebookModel(content, commit, params);
|
||||
}
|
||||
|
||||
return this.createFileModel(content, params);
|
||||
return this.createFileModel(content, commit, params);
|
||||
}
|
||||
|
||||
private createDirectoryModel(uri: string, gitHubFiles: IGitHubFile[] | undefined): IContent<"directory"> {
|
||||
private createDirectoryModel(
|
||||
uri: string,
|
||||
gitHubFiles: IGitHubFile[] | undefined,
|
||||
commit: IGitHubCommit
|
||||
): IContent<"directory"> {
|
||||
return {
|
||||
name: NotebookUtil.getName(uri),
|
||||
name: GitHubUtils.fromContentUri(uri).path,
|
||||
path: uri,
|
||||
type: "directory",
|
||||
writable: true, // TODO: tamitta: we don't know this info here
|
||||
created: "", // TODO: tamitta: we don't know this info here
|
||||
last_modified: "", // TODO: tamitta: we don't know this info here
|
||||
last_modified: commit.committer.date,
|
||||
mimetype: undefined,
|
||||
content: gitHubFiles?.map(
|
||||
(file: IGitHubFile) =>
|
||||
this.createContentModel(
|
||||
GitHubUtils.toContentUri(file.repo.owner, file.repo.name, file.branch.name, file.path),
|
||||
GitHubUtils.toContentUri(file.repo.owner.login, file.repo.name, file.branch.name, file.path),
|
||||
file,
|
||||
commit,
|
||||
{
|
||||
content: 0
|
||||
}
|
||||
@@ -334,12 +342,17 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private createNotebookModel(gitHubFile: IGitHubFile, params: Partial<IGetParams>): IContent<"notebook"> {
|
||||
const content: Notebook = gitHubFile.content && params.content !== 0 ? JSON.parse(gitHubFile.content) : undefined;
|
||||
private createNotebookModel(
|
||||
gitHubFile: IGitHubFile,
|
||||
commit: IGitHubCommit,
|
||||
params: Partial<IGetParams>
|
||||
): IContent<"notebook"> {
|
||||
const content: Notebook =
|
||||
gitHubFile.content && params.content !== 0 ? JSON.parse(atob(gitHubFile.content)) : undefined;
|
||||
return {
|
||||
name: gitHubFile.name,
|
||||
path: GitHubUtils.toContentUri(
|
||||
gitHubFile.repo.owner,
|
||||
gitHubFile.repo.owner.login,
|
||||
gitHubFile.repo.name,
|
||||
gitHubFile.branch.name,
|
||||
gitHubFile.path
|
||||
@@ -347,19 +360,23 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
type: "notebook",
|
||||
writable: true, // TODO: tamitta: we don't know this info here
|
||||
created: "", // TODO: tamitta: we don't know this info here
|
||||
last_modified: gitHubFile.commit.commitDate,
|
||||
last_modified: commit.committer.date,
|
||||
mimetype: content ? "application/x-ipynb+json" : undefined,
|
||||
content,
|
||||
format: content ? "json" : undefined
|
||||
};
|
||||
}
|
||||
|
||||
private createFileModel(gitHubFile: IGitHubFile, params: Partial<IGetParams>): IContent<"file"> {
|
||||
const content: string = gitHubFile.content && params.content !== 0 ? gitHubFile.content : undefined;
|
||||
private createFileModel(
|
||||
gitHubFile: IGitHubFile,
|
||||
commit: IGitHubCommit,
|
||||
params: Partial<IGetParams>
|
||||
): IContent<"file"> {
|
||||
const content: string = gitHubFile.content && params.content !== 0 ? atob(gitHubFile.content) : undefined;
|
||||
return {
|
||||
name: gitHubFile.name,
|
||||
path: GitHubUtils.toContentUri(
|
||||
gitHubFile.repo.owner,
|
||||
gitHubFile.repo.owner.login,
|
||||
gitHubFile.repo.name,
|
||||
gitHubFile.branch.name,
|
||||
gitHubFile.path
|
||||
@@ -367,7 +384,7 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
type: "file",
|
||||
writable: true, // TODO: tamitta: we don't know this info here
|
||||
created: "", // TODO: tamitta: we don't know this info here
|
||||
last_modified: gitHubFile.commit.commitDate,
|
||||
last_modified: commit.committer.date,
|
||||
mimetype: content ? "text/plain" : undefined,
|
||||
content,
|
||||
format: content ? "text" : undefined
|
||||
|
||||
@@ -713,7 +713,7 @@ class HostedExplorer {
|
||||
? storedDefaultTenantId.substring(DefaultDirectoryDropdownComponent.lastVisitedKey.length)
|
||||
: storedDefaultTenantId;
|
||||
|
||||
let defaultTenant: Tenant = _.find(tenants, t => t.tenantId === storedDefaultTenantId);
|
||||
let defaultTenant: Tenant = tenants.find(t => t.tenantId === storedDefaultTenantId);
|
||||
if (!defaultTenant) {
|
||||
defaultTenant = tenants[0];
|
||||
LocalStorageUtility.setEntryString(
|
||||
@@ -830,7 +830,7 @@ class HostedExplorer {
|
||||
const storedAccountId = LocalStorageUtility.getEntryString(StorageKey.DatabaseAccountId);
|
||||
const storedSubId = storedAccountId && storedAccountId.split("subscriptions/")[1].split("/")[0];
|
||||
|
||||
let defaultSub = _.find(subscriptions, s => s.subscriptionId === storedSubId);
|
||||
let defaultSub = subscriptions.find(s => s.subscriptionId === storedSubId);
|
||||
if (!defaultSub) {
|
||||
defaultSub = subscriptions[0];
|
||||
}
|
||||
@@ -932,7 +932,7 @@ class HostedExplorer {
|
||||
}
|
||||
|
||||
let storedDefaultAccountId = LocalStorageUtility.getEntryString(StorageKey.DatabaseAccountId);
|
||||
let defaultAccount = _.find(accounts, a => a.id === storedDefaultAccountId);
|
||||
let defaultAccount = accounts.find(a => a.id === storedDefaultAccountId);
|
||||
|
||||
if (!defaultAccount) {
|
||||
defaultAccount = accounts[0];
|
||||
|
||||
@@ -15,6 +15,7 @@ import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
|
||||
import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
|
||||
import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
|
||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||
import "./Explorer/Controls/Tabs/TabComponent.less";
|
||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||
import "../less/TableStyles/queryBuilder.less";
|
||||
import "../externals/jquery.dataTables.min.css";
|
||||
|
||||
@@ -86,7 +86,6 @@ export enum Action {
|
||||
CreateNewNotebook,
|
||||
OpenSampleNotebook,
|
||||
ExecuteCell,
|
||||
ExecuteCellPromptBtn,
|
||||
ExecuteAllCells,
|
||||
NotebookEnabled,
|
||||
NotebooksGitHubConnect,
|
||||
|
||||
@@ -7,10 +7,6 @@ export class GitHubUtils {
|
||||
// Custom scheme for github content
|
||||
private static readonly ContentUriPattern = /github:\/\/([^/]*)\/([^/]*)\/([^?]*)\?ref=(.*)/;
|
||||
|
||||
// https://github.com/<owner>/<repo>/blob/<branch>/<path>
|
||||
// We need to support this until we move to newer scheme for quickstarts
|
||||
private static readonly LegacyContentUriPattern = /https:\/\/github.com\/([^/]*)\/([^/]*)\/blob\/([^/]*)\/([^?]*)/;
|
||||
|
||||
public static toRepoFullName(owner: string, repo: string): string {
|
||||
return `${owner}/${repo}`;
|
||||
}
|
||||
@@ -31,7 +27,7 @@ export class GitHubUtils {
|
||||
public static fromContentUri(
|
||||
contentUri: string
|
||||
): undefined | { owner: string; repo: string; branch: string; path: string } {
|
||||
let matches = contentUri.match(GitHubUtils.ContentUriPattern);
|
||||
const matches = contentUri.match(GitHubUtils.ContentUriPattern);
|
||||
if (matches && matches.length > 4) {
|
||||
return {
|
||||
owner: matches[1],
|
||||
@@ -41,18 +37,6 @@ export class GitHubUtils {
|
||||
};
|
||||
}
|
||||
|
||||
matches = contentUri.match(GitHubUtils.LegacyContentUriPattern);
|
||||
if (matches && matches.length > 4) {
|
||||
console.log(`Using legacy github content uri scheme ${contentUri}`);
|
||||
|
||||
return {
|
||||
owner: matches[1],
|
||||
repo: matches[2],
|
||||
branch: matches[3],
|
||||
path: matches[4]
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,17 @@ export class JunoUtils {
|
||||
public static async getLikedNotebooks(authorizationToken: string): Promise<DataModels.LikedNotebooksJunoResponse> {
|
||||
//TODO: Add Get method once juno has it implemented
|
||||
return {
|
||||
likedNotebooksContent: [],
|
||||
likedNotebooksContent: await JunoUtils.getOfficialSampleNotebooks(),
|
||||
userMetadata: {
|
||||
likedNotebooks: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static async getOfficialSampleNotebooks(
|
||||
authorizationToken: string
|
||||
): Promise<DataModels.GitHubInfoJunoResponse[]> {
|
||||
public static async getOfficialSampleNotebooks(): Promise<DataModels.GitHubInfoJunoResponse[]> {
|
||||
try {
|
||||
const response = await window.fetch(config.JUNO_ENDPOINT + "/api/notebooks/galleries", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
authorization: authorizationToken
|
||||
}
|
||||
const response = await window.fetch(config.JUNO_ENDPOINT + "/api/galleries/notebooks", {
|
||||
method: "GET"
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Status code:" + response.status);
|
||||
@@ -40,21 +35,19 @@ export class JunoUtils {
|
||||
): Promise<DataModels.UserMetadata> {
|
||||
return undefined;
|
||||
//TODO: add userMetadata updation code
|
||||
// TODO: Make sure to throw error if failed
|
||||
}
|
||||
|
||||
public static async updateNotebookMetadata(
|
||||
authorizationToken: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata
|
||||
userMetadata: DataModels.NotebookMetadata
|
||||
): Promise<DataModels.NotebookMetadata> {
|
||||
return undefined;
|
||||
//TODO: add notebookMetadata updation code
|
||||
// TODO: Make sure to throw error if failed
|
||||
}
|
||||
|
||||
public static toPinnedRepo(item: RepoListItem): IPinnedRepo {
|
||||
return {
|
||||
owner: item.repo.owner,
|
||||
owner: item.repo.owner.login,
|
||||
name: item.repo.name,
|
||||
private: item.repo.private,
|
||||
branches: item.branches.map(element => ({ name: element.name }))
|
||||
@@ -63,7 +56,9 @@ export class JunoUtils {
|
||||
|
||||
public static toGitHubRepo(pinnedRepo: IPinnedRepo): IGitHubRepo {
|
||||
return {
|
||||
owner: pinnedRepo.owner,
|
||||
owner: {
|
||||
login: pinnedRepo.owner
|
||||
},
|
||||
name: pinnedRepo.name,
|
||||
private: pinnedRepo.private
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { Platform } from "../../Config";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
|
||||
export interface StartUploadMessageParams {
|
||||
files: FileList;
|
||||
@@ -12,7 +12,7 @@ export interface DocumentClientParams {
|
||||
masterKey: string;
|
||||
endpoint: string;
|
||||
accessToken: string;
|
||||
platform: Platform;
|
||||
platform: PlatformType;
|
||||
databaseAccount: DatabaseAccount;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "babel-polyfill";
|
||||
import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./definitions";
|
||||
import { CosmosClient } from "../../Common/CosmosClient";
|
||||
import { config } from "../../Config";
|
||||
|
||||
let numUploadsSuccessful = 0;
|
||||
let numUploadsFailed = 0;
|
||||
@@ -34,7 +33,8 @@ onmessage = (event: MessageEvent) => {
|
||||
CosmosClient.endpoint(clientParams.endpoint);
|
||||
CosmosClient.accessToken(clientParams.accessToken);
|
||||
CosmosClient.databaseAccount(clientParams.databaseAccount);
|
||||
config.platform = clientParams.platform;
|
||||
self.dataExplorerPlatform = clientParams.platform;
|
||||
console.log(event);
|
||||
if (!!files && files.length > 0) {
|
||||
numFiles = files.length;
|
||||
for (let i = 0; i < numFiles; i++) {
|
||||
@@ -106,7 +106,6 @@ function createDocumentsFromFile(fileName: string, documentContent: string): voi
|
||||
triggerCreateDocument(content);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
recordUploadDetailErrorForFile(fileName, e.message);
|
||||
transmitResultIfUploadComplete();
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ module.exports = function(env = {}, argv = {}) {
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "notebookViewer.html",
|
||||
template: "src/NotebookViewer/notebookViewer.html",
|
||||
template: "src/Explorer/Controls/NotebookViewer/notebookViewer.html",
|
||||
chunks: ["notebookViewer"]
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
@@ -175,7 +175,7 @@ module.exports = function(env = {}, argv = {}) {
|
||||
hostedExplorer: "./src/HostedExplorer.ts",
|
||||
heatmap: "./src/Controls/Heatmap/Heatmap.ts",
|
||||
terminal: "./src/Terminal/index.ts",
|
||||
notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx",
|
||||
notebookViewer: "./src/Explorer/Controls/NotebookViewer/NotebookViewer.tsx",
|
||||
galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx",
|
||||
connectToGitHub: "./src/GitHub/GitHubConnector.ts"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user