diff --git a/.github/workflows/docker_build_push.sh b/.github/workflows/docker_build_push.sh index 1c3723611f364..3c625350bf1ba 100755 --- a/.github/workflows/docker_build_push.sh +++ b/.github/workflows/docker_build_push.sh @@ -90,4 +90,5 @@ else docker logout docker login --username "${DOCKERHUB_USER}" --password "${DOCKERHUB_TOKEN}" docker push --all-tags "${REPO_NAME}" + docker push --all-tags "${REPO_NAME}-websocket" fi diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bee3a24a1e781..cab647121f0cf 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -106,7 +106,7 @@ This statement thanks the following, on which it draws for content and inspirati # Slack Community Guidelines -If you decide to join the [Community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI7jKWp~xc2zYRe~NqiY9Q), please adhere to the following rules: +If you decide to join the [Community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-1jp6hjzrq-H0PlFtToyLWuPiJDuRWCNw), please adhere to the following rules: **1. Treat everyone in the community with respect.** diff --git a/README.md b/README.md index b547030669480..fb1d155e26b40 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ under the License. [![PyPI version](https://badge.fury.io/py/apache-superset.svg)](https://badge.fury.io/py/apache-superset) [![Coverage Status](https://codecov.io/github/apache/superset/coverage.svg?branch=master)](https://codecov.io/github/apache/superset) [![PyPI](https://img.shields.io/pypi/pyversions/apache-superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/apache-superset) -[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI7jKWp~xc2zYRe~NqiY9Q) +[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://join.slack.com/t/apache-superset/shared_invite/zt-1jp6hjzrq-H0PlFtToyLWuPiJDuRWCNw) [![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.apache.org) =0.2.2 +clickhouse-connect>=0.4.1 ``` If running Superset using Docker Compose, add the following to your `./docker/requirements-local.txt` file: ``` -clickhouse-sqlalchemy>=0.2.2 +clickhouse-connect>=0.4.1 ``` The recommended connector library for ClickHouse is -[sqlalchemy-clickhouse](https://github.com/cloudflare/sqlalchemy-clickhouse). +[clickhouse-connect](https://github.com/ClickHouse/clickhouse-connect). The expected connection string is formatted as follows: ``` -clickhouse+native://:@:/[?options…]clickhouse://{username}:{password}@{hostname}:{port}/{database} +clickhousedb://:@:/[?options…]clickhouse://{username}:{password}@{hostname}:{port}/{database} ``` Here's a concrete example of a real connection string: ``` -clickhouse+native://demo:demo@github.demo.trial.altinity.cloud/default?secure=true +clickhousedb://demo:demo@github.demo.trial.altinity.cloud/default?secure=true ``` -If you're using Clickhouse locally on your computer, you can get away with using a native protocol URL that +If you're using Clickhouse locally on your computer, you can get away with using a http protocol URL that uses the default user without a password (and doesn't encrypt the connection): ``` -clickhouse+native://localhost/default +clickhousedb://localhost/default ``` diff --git a/docs/docs/databases/dynamodb.mdx b/docs/docs/databases/dynamodb.mdx new file mode 100644 index 0000000000000..4cb9e1e4f8077 --- /dev/null +++ b/docs/docs/databases/dynamodb.mdx @@ -0,0 +1,20 @@ +--- +title: Amazon DynamoDB +hide_title: true +sidebar_position: 4 +version: 1 +--- + +## AWS DynamoDB + +### PyDynamoDB + +[PyDynamoDB](https://pypi.org/project/PyDynamoDB/) is a Python DB API 2.0 (PEP 249) client for Amazon DynamoDB. + +The connection string for Amazon DynamoDB is as follows: + +``` +dynamodb://{aws_access_key_id}:{aws_secret_access_key}@dynamodb.{region_name}.amazonaws.com:443?connector=superset +``` + +To get more documentation, please visit: [PyDynamoDB WIKI](https://github.com/passren/PyDynamoDB/wiki/5.-Superset). diff --git a/docs/docs/databases/installing-database-drivers.mdx b/docs/docs/databases/installing-database-drivers.mdx index c2dc1159e6566..62244b3d6d841 100644 --- a/docs/docs/databases/installing-database-drivers.mdx +++ b/docs/docs/databases/installing-database-drivers.mdx @@ -23,6 +23,7 @@ A list of some of the recommended packages. | Database | PyPI package | Connection String | | --------------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | [Amazon Athena](/docs/databases/athena) | `pip install "PyAthenaJDBC>1.0.9` , `pip install "PyAthena>1.2.0` | `awsathena+rest://{aws_access_key_id}:{aws_secret_access_key}@athena.{region_name}.amazonaws.com/{ ` | +| [Amazon DynamoDB](/docs/databases/dynamodb) | `pip install "PyDynamoDB>=0.4.2` | `dynamodb://{access_key_id}:{secret_access_key}@dynamodb.{region_name}.amazonaws.com?connector=superset` | | [Amazon Redshift](/docs/databases/redshift) | `pip install sqlalchemy-redshift` | ` redshift+psycopg2://:@:5439/` | | [Apache Drill](/docs/databases/drill) | `pip install sqlalchemy-drill` | `drill+sadrill:// For JDBC drill+jdbc://` | | [Apache Druid](/docs/databases/druid) | `pip install pydruid` | `druid://:@:/druid/v2/sql` | @@ -35,7 +36,7 @@ A list of some of the recommended packages. | [Ascend.io](/docs/databases/ascend) | `pip install impyla` | `ascend://{username}:{password}@{hostname}:{port}/{database}?auth_mechanism=PLAIN;use_ssl=true` | | [Azure MS SQL](/docs/databases/sql-server) | `pip install pymssql` | `mssql+pymssql://UserName@presetSQL:TestPassword@presetSQL.database.windows.net:1433/TestSchema` | | [Big Query](/docs/databases/bigquery) | `pip install pybigquery` | `bigquery://{project_id}` | -| [ClickHouse](/docs/databases/clickhouse) | `pip install clickhouse-sqlalchemy` | `clickhouse+native://{username}:{password}@{hostname}:{port}/{database}` | +| [ClickHouse](/docs/databases/clickhouse) | `pip install clickhouse-connect` | `clickhousedb://{username}:{password}@{hostname}:{port}/{database}` | | [CockroachDB](/docs/databases/cockroachdb) | `pip install cockroachdb` | `cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable` | | [Dremio](/docs/databases/dremio) | `pip install sqlalchemy_dremio` | `dremio://user:pwd@host:31010/` | | [Elasticsearch](/docs/databases/elasticsearch) | `pip install elasticsearch-dbapi` | `elasticsearch+http://{user}:{password}@{host}:9200/` | @@ -48,7 +49,7 @@ A list of some of the recommended packages. | [MySQL](/docs/databases/mysql) | `pip install mysqlclient` | `mysql://:@/` | | [Oracle](/docs/databases/oracle) | `pip install cx_Oracle` | `oracle://` | | [PostgreSQL](/docs/databases/postgres) | `pip install psycopg2` | `postgresql://:@/` | -| [Trino](/docs/databases/trino) | `pip install sqlalchemy-trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` | +| [Trino](/docs/databases/trino) | `pip install trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` | | [Presto](/docs/databases/presto) | `pip install pyhive` | `presto://` | | [SAP Hana](/docs/databases/hana) | `pip install hdbcli sqlalchemy-hana or pip install apache-superset[hana]` | `hana://{username}:{password}@{host}:{port}` | | [Snowflake](/docs/databases/snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` | diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 2934afa62db9d..2df9fdf130574 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -211,7 +211,7 @@ const config = { }, { label: 'Slack', - href: 'https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI7jKWp~xc2zYRe~NqiY9Q', + href: 'https://join.slack.com/t/apache-superset/shared_invite/zt-1jp6hjzrq-H0PlFtToyLWuPiJDuRWCNw', }, { label: 'Mailing List', diff --git a/docs/src/pages/community.tsx b/docs/src/pages/community.tsx index 1cd9830dbf261..7e8630e1bf476 100644 --- a/docs/src/pages/community.tsx +++ b/docs/src/pages/community.tsx @@ -23,7 +23,7 @@ import Layout from '@theme/Layout'; const links = [ [ - 'https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI7jKWp~xc2zYRe~NqiY9Q', + 'https://join.slack.com/t/apache-superset/shared_invite/zt-1jp6hjzrq-H0PlFtToyLWuPiJDuRWCNw', 'Slack', 'interact with other Superset users and community members', ], diff --git a/setup.py b/setup.py index 5f18427f47850..d2b950c6a4cd9 100644 --- a/setup.py +++ b/setup.py @@ -130,7 +130,7 @@ def get_git_sha() -> str: "pybigquery>=0.4.10", "google-cloud-bigquery>=2.4.0", ], - "clickhouse": ["clickhouse-sqlalchemy>=0.2.2, <0.3"], + "clickhouse": ["clickhouse-connect>=0.4.1, <0.5"], "cockroachdb": ["cockroachdb>=0.3.5, <0.4"], "cors": ["flask-cors>=2.0.0"], "crate": ["crate[sqlalchemy]>=0.26.0, <0.27"], @@ -142,6 +142,7 @@ def get_git_sha() -> str: "dremio": ["sqlalchemy-dremio>=1.1.5, <1.3"], "drill": ["sqlalchemy-drill==0.1.dev"], "druid": ["pydruid>=0.6.5,<0.7"], + "dynamodb": ["pydynamodb>=0.4.2"], "solr": ["sqlalchemy-solr >= 0.2.0"], "elasticsearch": ["elasticsearch-dbapi>=0.2.9, <0.3.0"], "exasol": ["sqlalchemy-exasol >= 2.4.0, <3.0"], diff --git a/superset-frontend/.storybook/main.js b/superset-frontend/.storybook/main.js index 814e53cf58411..35783dd85d286 100644 --- a/superset-frontend/.storybook/main.js +++ b/superset-frontend/.storybook/main.js @@ -24,8 +24,8 @@ module.exports = { builder: 'webpack5', }, stories: [ - '../src/@(components|common|filters|explore|views)/**/*.stories.@(tsx|jsx)', - '../src/@(components|common|filters|explore|views)/**/*.*.@(mdx)', + '../src/@(components|common|filters|explore|views|dashboard)/**/*.stories.@(tsx|jsx)', + '../src/@(components|common|filters|explore|views|dashboard)/**/*.*.@(mdx)', ], addons: [ '@storybook/addon-essentials', diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts index caf938b33cf47..64522a2105405 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts @@ -65,7 +65,7 @@ function openModalFromChartContext(targetMenuItem: string) { .first() .click(); } - + cy.getBySel('metadata-bar').should('be.visible'); cy.wait('@samples'); } @@ -196,6 +196,23 @@ describe('Drill to detail modal', () => { .then($rows => { expect($rows).to.contain('Victoria'); }); + + // verify scroll top on pagination + cy.getBySelLike('Number-modal') + .find('.table-condensed') + .scrollTo(0, 100); + + cy.get("[role='rowgroup'] [role='row']") + .contains('Miguel') + .should('not.be.visible'); + + cy.get(".pagination-container [role='navigation'] [role='button']") + .eq(1) + .click(); + + cy.get("[role='rowgroup'] [role='row']") + .contains('Aaron') + .should('be.visible'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts index 3d9f92962d041..afcd5b2f60589 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts @@ -457,6 +457,7 @@ describe('Native filters', () => { search.split('').slice(1, search.length).join(''); cy.location().then(loc => { + cy.url().should('contain', 'native_filters_key'); const queryParams = qs.parse(removeFirstChar(loc.search)); filterKey = queryParams.native_filters_key as string; expect(typeof filterKey).eq('string'); @@ -465,6 +466,7 @@ describe('Native filters', () => { addCountryNameFilter(); saveNativeFilterSettings([SAMPLE_CHART]); cy.location().then(loc => { + cy.url().should('contain', 'native_filters_key'); const queryParams = qs.parse(removeFirstChar(loc.search)); const newfilterKey = queryParams.native_filters_key; expect(newfilterKey).eq(filterKey); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts index 076b0438ae8a6..89b5c85052036 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts @@ -158,6 +158,10 @@ export function interceptDatasets() { cy.intercept('GET', `/api/v1/dashboard/*/datasets`).as('getDatasets'); } +export function interceptDashboardasync() { + cy.intercept('GET', `/dashboardasync/api/read*`).as('getDashboardasync'); +} + export function setFilter(filter: string, option: string) { interceptFiltering(); @@ -346,8 +350,8 @@ export function cancelNativeFilterSettings() { .should('be.visible') .click(); cy.get(nativeFilters.modal.alertXUnsavedFilters) - .should('have.text', 'There are unsaved changes.') - .should('be.visible'); + .should('be.visible') + .should('have.text', 'There are unsaved changes.'); cy.get(nativeFilters.modal.footer) .find(nativeFilters.modal.yesCancelButton) .contains('cancel') @@ -455,13 +459,17 @@ export function inputNativeFilterDefaultValue(defaultValue: string) { cy.contains('Filter has default value').click(); cy.contains('Default value is required').should('be.visible'); cy.get(nativeFilters.modal.container).within(() => { - cy.get(nativeFilters.filterConfigurationSections.filterPlaceholder) - .contains('options') - .should('be.visible'); + cy.get( + nativeFilters.filterConfigurationSections.filterPlaceholder, + ).contains('options'); cy.get(nativeFilters.filterConfigurationSections.collapsedSectionContainer) - .first() - .get(nativeFilters.filtersPanel.columnEmptyInput) - .type(`${defaultValue}{enter}`); + .eq(1) + .within(() => { + cy.get('.ant-select-selection-search-input').type( + `${defaultValue}{enter}`, + { force: true }, + ); + }); }); } diff --git a/superset-frontend/cypress-base/cypress/integration/explore/utils.ts b/superset-frontend/cypress-base/cypress/integration/explore/utils.ts index eb8631c753ee6..3eac4f97be090 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/utils.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/utils.ts @@ -17,7 +17,10 @@ * under the License. */ -import { interceptGet as interceptDashboardGet } from '../dashboard/utils'; +import { + interceptGet as interceptDashboardGet, + interceptDashboardasync, +} from '../dashboard/utils'; export function interceptFiltering() { cy.intercept('GET', `/api/v1/chart/?q=*`).as('filtering'); @@ -58,10 +61,13 @@ export function setFilter(filter: string, option: string) { export function saveChartToDashboard(dashboardName: string) { interceptDashboardGet(); + interceptDashboardasync(); interceptUpdate(); interceptExploreGet(); cy.getBySel('query-save-button').click(); + cy.wait('@getDashboardasync'); + cy.getBySelLike('chart-modal').should('be.visible'); cy.get( '[data-test="save-chart-modal-select-dashboard-form"] [aria-label="Select a dashboard"]', ) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index cf956ff74d4b2..34dc1c17845ae 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -53,7 +53,7 @@ "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "ace-builds": "^1.4.14", - "antd": "^4.9.4", + "antd": "4.10.3", "array-move": "^2.2.1", "babel-plugin-typescript-to-proptypes": "^2.0.0", "bootstrap": "^3.4.1", @@ -390,22 +390,6 @@ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz", "integrity": "sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==" }, - "node_modules/@ant-design/react-slick": { - "version": "0.27.14", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.27.14.tgz", - "integrity": "sha512-s6JVexqFmU5rs5Pm828ojtm5rCp8jDXyrc5OxEtCE2z58SIyQlkpnU9BJh98LEeBZyj02WFkGN8CWpSaD+G4PA==", - "dependencies": { - "@babel/runtime": "^7.10.4", - "classnames": "^2.2.5", - "json2mq": "^0.2.0", - "lodash": "^4.17.15", - "resize-observer-polyfill": "^1.5.0" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", - "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@applitools/dom-capture": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@applitools/dom-capture/-/dom-capture-11.1.0.tgz", @@ -3660,11 +3644,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", - "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.10" }, "engines": { "node": ">=6.9.0" @@ -20374,50 +20358,49 @@ } }, "node_modules/antd": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.9.4.tgz", - "integrity": "sha512-kieGi1Isb/ddnn9E/AJVFCUgSZIqDv6HtFg7r5WWI0s6zf+nfCOtpes0oX8TdHO6mE/dL39pJG52aHNe8MwkJg==", + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.10.3.tgz", + "integrity": "sha512-J/IZvW15MwTmUxK/AWFkSU51T1Hyn4e0GchJWlIe7+FrPpLoTgLf9/Cx3mgxiooHfE9OfvnYvvRli1VxHH6H0Q==", "dependencies": { "@ant-design/colors": "^5.0.0", "@ant-design/icons": "^4.3.0", - "@ant-design/react-slick": "~0.27.0", + "@ant-design/react-slick": "~0.28.1", "@babel/runtime": "^7.11.2", "array-tree-filter": "^2.1.0", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "lodash": "^4.17.20", "moment": "^2.25.3", - "omit.js": "^2.0.2", "rc-cascader": "~1.4.0", "rc-checkbox": "~2.3.0", "rc-collapse": "~3.1.0", - "rc-dialog": "~8.4.0", - "rc-drawer": "~4.1.0", + "rc-dialog": "~8.5.1", + "rc-drawer": "~4.2.0", "rc-dropdown": "~3.2.0", - "rc-field-form": "~1.17.0", - "rc-image": "~4.2.0", + "rc-field-form": "~1.17.3", + "rc-image": "~5.0.2", "rc-input-number": "~6.1.0", "rc-mentions": "~1.5.0", "rc-menu": "~8.10.0", "rc-motion": "^2.4.0", "rc-notification": "~4.5.2", "rc-pagination": "~3.1.2", - "rc-picker": "~2.4.1", + "rc-picker": "~2.5.1", "rc-progress": "~3.1.0", "rc-rate": "~2.9.0", - "rc-resize-observer": "^0.2.3", - "rc-select": "~11.5.3", - "rc-slider": "~9.6.1", + "rc-resize-observer": "^1.0.0", + "rc-select": "~12.1.0", + "rc-slider": "~9.7.1", "rc-steps": "~4.1.0", "rc-switch": "~3.2.0", - "rc-table": "~7.11.0", + "rc-table": "~7.12.0", "rc-tabs": "~11.7.0", "rc-textarea": "~0.3.0", "rc-tooltip": "~5.0.0", - "rc-tree": "~4.0.0", - "rc-tree-select": "~4.2.0", - "rc-upload": "~3.3.1", - "rc-util": "^5.1.0", + "rc-tree": "~4.1.0", + "rc-tree-select": "~4.3.0", + "rc-upload": "~3.3.4", + "rc-util": "^5.7.0", "scroll-into-view-if-needed": "^2.2.25", "warning": "^4.0.3" }, @@ -20457,11 +20440,266 @@ "react": ">=16.0.0" } }, + "node_modules/antd/node_modules/@ant-design/react-slick": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", + "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-dialog": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.5.3.tgz", + "integrity": "sha512-zoamT8L6+rBwnwjPlrZRxiHCHQXrTcWZD3a6ruoqEdUKP1KgO0eSjMDH9WlF3WEPYMVnb2G5SrjHrhnwgPDu5w==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.6.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-drawer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.2.2.tgz", + "integrity": "sha512-zw48FATkAmJrEnfeRWiMqvKAzqGzUDLN1UXlluB7q7GgbR6mJFvc+QsmNrgxsFuMz86Lh9mKSIi7rXlPINmuzw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.7.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-image": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.0.2.tgz", + "integrity": "sha512-bNCOGxo9ICe2S+MuVQtxVjk2esL0QJX4YcUB10S98z8CWO1sswySH6inH69YU778aCXs8/nKhtZMUmiU1To0bQ==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.5.1", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-picker": { + "version": "2.5.19", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz", + "integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-select": { + "version": "12.1.13", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.13.tgz", + "integrity": "sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.0.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.9.8", + "rc-virtual-list": "^3.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-select/node_modules/rc-overflow": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.8.tgz", + "integrity": "sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.19.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-select/node_modules/rc-virtual-list": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.11.tgz", + "integrity": "sha512-BvUUH60kkeTBPigN5F89HtGaA5jSP4y2aM6cJ4dk9Y42I9yY+h6i08wF6UKeDcxdfOU8j3I5HxkSS/xA77J3wA==", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.15.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-slider": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", + "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.16.1", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-table": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.12.5.tgz", + "integrity": "sha512-XV4m5h0W+NjGkNzvp5ahOhYHyNG8oPNV9pTLre2EsfmyStXUJBICyfkNID7WZulMdCehv/Wa3MdqXwZ4EsJchw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tooltip": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.0.2.tgz", + "integrity": "sha512-A4FejSG56PzYtSNUU4H1pVzfhtkV/+qMT2clK0CsSj+9mbc4USEtpWeX6A/jjVL+goBOMKj8qlH7BCZmZWh/Nw==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tree": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.1.5.tgz", + "integrity": "sha512-q2vjcmnBDylGZ9/ZW4F9oZMKMJdbFWC7um+DAQhZG1nqyg1iwoowbBggUDUaUOEryJP+08bpliEAYnzJXbI5xQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.0.0", + "rc-virtual-list": "^3.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-tree-select": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.3.3.tgz", + "integrity": "sha512-0tilOHLJA6p+TNg4kD559XnDX3PTEYuoSF7m7ryzFLAYvdEEPtjn0QZc5z6L0sMKBiBlj8a2kf0auw8XyHU3lA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "^12.0.0", + "rc-tree": "^4.0.0", + "rc-util": "^5.0.5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-tree/node_modules/rc-virtual-list": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.11.tgz", + "integrity": "sha512-BvUUH60kkeTBPigN5F89HtGaA5jSP4y2aM6cJ4dk9Y42I9yY+h6i08wF6UKeDcxdfOU8j3I5HxkSS/xA77J3wA==", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.15.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/antd/node_modules/rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", + "version": "5.24.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", "dependencies": { + "@babel/runtime": "^7.18.3", "react-is": "^16.12.0", "shallowequal": "^1.1.0" }, @@ -26592,9 +26830,9 @@ } }, "node_modules/date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", "engines": { "node": ">=0.11" }, @@ -37601,7 +37839,7 @@ "node_modules/json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", - "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", "dependencies": { "string-convert": "^0.2.0" } @@ -44578,34 +44816,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/rc-dialog": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.4.5.tgz", - "integrity": "sha512-0a1Uuy1BRBTdIkfR1VE91kis6dBui7tAIPaQQLj28vBdGg9IqVkiLguCdaDW+4E4vZediePz49PKFbLkx2PL5Q==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-motion": "^2.3.0", - "rc-util": "^5.0.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-drawer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.1.0.tgz", - "integrity": "sha512-kjeQFngPjdzAFahNIV0EvEBoIKMOnvUsAxpkSPELoD/1DuR4nLafom5ryma+TIxGwkFJ92W6yjsMi1U9aiOTeQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-util": "^5.0.1" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/rc-dropdown": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.0.tgz", @@ -44636,22 +44846,6 @@ "react": ">= 16.9.0" } }, - "node_modules/rc-image": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-4.2.0.tgz", - "integrity": "sha512-yGqq6wPrIn86hMfC1Hl7M3NNS6zqnl9dvFWJg/StuI86jZBU0rm9rePTfKs+4uiwU3HXxpfsXlaG2p8GWRDLiw==", - "dependencies": { - "@ant-design/icons": "^4.2.2", - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-dialog": "~8.4.0", - "rc-util": "^5.0.6" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, "node_modules/rc-input-number": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-6.1.2.tgz", @@ -44783,46 +44977,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc-picker": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.4.3.tgz", - "integrity": "sha512-tOIHslTQKpoGNmbpp6YOBwS39dQSvtAuhOm3bWCkkc4jCqUqeR/velCwqefZX1BX4+t1gUMc1dIia9XvOKrEkg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "date-fns": "^2.15.0", - "dayjs": "^1.8.30", - "moment": "^2.24.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-picker/node_modules/rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "dependencies": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-picker/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/rc-progress": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.1.tgz", @@ -44868,46 +45022,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc-select": { - "version": "11.5.3", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-11.5.3.tgz", - "integrity": "sha512-ASSO4J/ayfbQQ+KOEounIMGhySDHpQtrIuH1WEABOBy8HgKec8kOLmyLH+YIXSUDnTf/gtxmflgFtl7sQ9pkSw==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1", - "rc-virtual-list": "^3.2.0", - "warning": "^4.0.3" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/rc-slider": { - "version": "9.6.5", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.6.5.tgz", - "integrity": "sha512-XRUJDK668hy8MwGnHzZlXCQXXIOUnEs4m2vwk1jgDILVBxI0GwGOlC6T499pYY+NEWg8YgdCOAucFs/+X5WHpg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-tooltip": "^5.0.1", - "rc-util": "^5.0.0", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, "node_modules/rc-steps": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.3.tgz", @@ -44939,43 +45053,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc-table": { - "version": "7.11.3", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.11.3.tgz", - "integrity": "sha512-YyZry1CdqUrcH7MmWtLQZVvVZWbmTEbI5m650AZ+zYw4D5VF701samkMYl5z/H9yQFr+ugvDtXcya+e3vwRkMQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-resize-observer": "^0.2.0", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-table/node_modules/rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "dependencies": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-table/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/rc-tabs": { "version": "11.7.2", "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.7.2.tgz", @@ -45029,50 +45106,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc-tooltip": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.0.1.tgz", - "integrity": "sha512-3AnxhUS0j74xAV3khrKw8o6rg+Ima3nw09DJBezMPnX3ImQUAnayWsPSlN1mEnihjA43rcFkGM1emiKE+CXyMQ==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "rc-trigger": "^5.0.0" - } - }, - "node_modules/rc-tree": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.0.0.tgz", - "integrity": "sha512-C2xlkA+/IypkHBPzbpAJGVWJh2HjeRbYCusA/m5k09WT6hQT0nC7LtLVmnb7QZecdBQPhoOgQh8gPwBR+xEMjQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-util": "^5.0.0", - "rc-virtual-list": "^3.0.1" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/rc-tree-select": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.2.0.tgz", - "integrity": "sha512-VrrvBiOov6WR44RTGMqSw1Dmodg6Y++EH6a6R0ew43qsV4Ob0FGYRgoX811kImtt2Z+oAPJ6zZXN4WKtsQd3Gw==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-select": "^11.1.1", - "rc-tree": "^4.0.0", - "rc-util": "^5.0.5" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, "node_modules/rc-trigger": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.0.tgz", @@ -45152,41 +45185,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/rc-virtual-list": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.2.3.tgz", - "integrity": "sha512-uEeYDQWwQhxR97SekPeGRbzPtHSbSpw/mYb6QpZZ9bA43kf7s1socV3fD3ySYhQVzo0I+/IUD9jFGit6FbM0WA==", - "dependencies": { - "classnames": "^2.2.6", - "rc-resize-observer": "^0.2.3", - "rc-util": "^5.0.7" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/rc-virtual-list/node_modules/rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "dependencies": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/rc-virtual-list/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/re-resizable": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.6.1.tgz", @@ -46761,9 +46759,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -49586,7 +49584,7 @@ "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" }, "node_modules/string-hash": { "version": "1.1.3", @@ -55739,7 +55737,7 @@ "@testing-library/react-hooks": "^5.0.3", "@testing-library/user-event": "^12.7.0", "ace-builds": "^1.4.14", - "antd": "^4.9.4", + "antd": "4.10.3", "brace": "^0.11.1", "memoize-one": "^5.1.1", "react": "^16.13.1", @@ -55894,7 +55892,7 @@ "@storybook/addons": "^6.3.12", "@storybook/react": "^6.3.12", "@types/react-loadable": "^5.5.3", - "antd": "^4.9.4", + "antd": "4.10.3", "bootstrap": "^3.4.1", "core-js": "3.8.3", "gh-pages": "^3.0.0", @@ -57571,18 +57569,6 @@ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz", "integrity": "sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==" }, - "@ant-design/react-slick": { - "version": "0.27.14", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.27.14.tgz", - "integrity": "sha512-s6JVexqFmU5rs5Pm828ojtm5rCp8jDXyrc5OxEtCE2z58SIyQlkpnU9BJh98LEeBZyj02WFkGN8CWpSaD+G4PA==", - "requires": { - "@babel/runtime": "^7.10.4", - "classnames": "^2.2.5", - "json2mq": "^0.2.0", - "lodash": "^4.17.15", - "resize-observer-polyfill": "^1.5.0" - } - }, "@applitools/dom-capture": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@applitools/dom-capture/-/dom-capture-11.1.0.tgz", @@ -59869,11 +59855,11 @@ } }, "@babel/runtime": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", - "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.10" } }, "@babel/runtime-corejs2": { @@ -70278,7 +70264,7 @@ "@storybook/addons": "^6.3.12", "@storybook/react": "^6.3.12", "@types/react-loadable": "^5.5.3", - "antd": "^4.9.4", + "antd": "4.10.3", "babel-loader": "^8.1.0", "bootstrap": "^3.4.1", "chromatic": "^5.4.0", @@ -73835,50 +73821,49 @@ } }, "antd": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.9.4.tgz", - "integrity": "sha512-kieGi1Isb/ddnn9E/AJVFCUgSZIqDv6HtFg7r5WWI0s6zf+nfCOtpes0oX8TdHO6mE/dL39pJG52aHNe8MwkJg==", + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.10.3.tgz", + "integrity": "sha512-J/IZvW15MwTmUxK/AWFkSU51T1Hyn4e0GchJWlIe7+FrPpLoTgLf9/Cx3mgxiooHfE9OfvnYvvRli1VxHH6H0Q==", "requires": { "@ant-design/colors": "^5.0.0", "@ant-design/icons": "^4.3.0", - "@ant-design/react-slick": "~0.27.0", + "@ant-design/react-slick": "~0.28.1", "@babel/runtime": "^7.11.2", "array-tree-filter": "^2.1.0", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "lodash": "^4.17.20", "moment": "^2.25.3", - "omit.js": "^2.0.2", "rc-cascader": "~1.4.0", "rc-checkbox": "~2.3.0", "rc-collapse": "~3.1.0", - "rc-dialog": "~8.4.0", - "rc-drawer": "~4.1.0", + "rc-dialog": "~8.5.1", + "rc-drawer": "~4.2.0", "rc-dropdown": "~3.2.0", - "rc-field-form": "~1.17.0", - "rc-image": "~4.2.0", + "rc-field-form": "~1.17.3", + "rc-image": "~5.0.2", "rc-input-number": "~6.1.0", "rc-mentions": "~1.5.0", "rc-menu": "~8.10.0", "rc-motion": "^2.4.0", "rc-notification": "~4.5.2", "rc-pagination": "~3.1.2", - "rc-picker": "~2.4.1", + "rc-picker": "~2.5.1", "rc-progress": "~3.1.0", "rc-rate": "~2.9.0", - "rc-resize-observer": "^0.2.3", - "rc-select": "~11.5.3", - "rc-slider": "~9.6.1", + "rc-resize-observer": "^1.0.0", + "rc-select": "~12.1.0", + "rc-slider": "~9.7.1", "rc-steps": "~4.1.0", "rc-switch": "~3.2.0", - "rc-table": "~7.11.0", + "rc-table": "~7.12.0", "rc-tabs": "~11.7.0", "rc-textarea": "~0.3.0", "rc-tooltip": "~5.0.0", - "rc-tree": "~4.0.0", - "rc-tree-select": "~4.2.0", - "rc-upload": "~3.3.1", - "rc-util": "^5.1.0", + "rc-tree": "~4.1.0", + "rc-tree-select": "~4.3.0", + "rc-upload": "~3.3.4", + "rc-util": "^5.7.0", "scroll-into-view-if-needed": "^2.2.25", "warning": "^4.0.3" }, @@ -73904,11 +73889,190 @@ "rc-util": "^5.0.1" } }, + "@ant-design/react-slick": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", + "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", + "requires": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.0" + } + }, + "rc-dialog": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.5.3.tgz", + "integrity": "sha512-zoamT8L6+rBwnwjPlrZRxiHCHQXrTcWZD3a6ruoqEdUKP1KgO0eSjMDH9WlF3WEPYMVnb2G5SrjHrhnwgPDu5w==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.6.1" + } + }, + "rc-drawer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.2.2.tgz", + "integrity": "sha512-zw48FATkAmJrEnfeRWiMqvKAzqGzUDLN1UXlluB7q7GgbR6mJFvc+QsmNrgxsFuMz86Lh9mKSIi7rXlPINmuzw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.7.0" + } + }, + "rc-image": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.0.2.tgz", + "integrity": "sha512-bNCOGxo9ICe2S+MuVQtxVjk2esL0QJX4YcUB10S98z8CWO1sswySH6inH69YU778aCXs8/nKhtZMUmiU1To0bQ==", + "requires": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.5.1", + "rc-util": "^5.0.6" + } + }, + "rc-picker": { + "version": "2.5.19", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz", + "integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + } + }, + "rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + } + }, + "rc-select": { + "version": "12.1.13", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.13.tgz", + "integrity": "sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.0.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.9.8", + "rc-virtual-list": "^3.2.0" + }, + "dependencies": { + "rc-overflow": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.8.tgz", + "integrity": "sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==", + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.19.2" + } + }, + "rc-virtual-list": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.11.tgz", + "integrity": "sha512-BvUUH60kkeTBPigN5F89HtGaA5jSP4y2aM6cJ4dk9Y42I9yY+h6i08wF6UKeDcxdfOU8j3I5HxkSS/xA77J3wA==", + "requires": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.15.0" + } + } + } + }, + "rc-slider": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", + "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.16.1", + "shallowequal": "^1.1.0" + } + }, + "rc-table": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.12.5.tgz", + "integrity": "sha512-XV4m5h0W+NjGkNzvp5ahOhYHyNG8oPNV9pTLre2EsfmyStXUJBICyfkNID7WZulMdCehv/Wa3MdqXwZ4EsJchw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + } + }, + "rc-tooltip": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.0.2.tgz", + "integrity": "sha512-A4FejSG56PzYtSNUU4H1pVzfhtkV/+qMT2clK0CsSj+9mbc4USEtpWeX6A/jjVL+goBOMKj8qlH7BCZmZWh/Nw==", + "requires": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + } + }, + "rc-tree": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.1.5.tgz", + "integrity": "sha512-q2vjcmnBDylGZ9/ZW4F9oZMKMJdbFWC7um+DAQhZG1nqyg1iwoowbBggUDUaUOEryJP+08bpliEAYnzJXbI5xQ==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.0.0", + "rc-virtual-list": "^3.0.1" + }, + "dependencies": { + "rc-virtual-list": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.11.tgz", + "integrity": "sha512-BvUUH60kkeTBPigN5F89HtGaA5jSP4y2aM6cJ4dk9Y42I9yY+h6i08wF6UKeDcxdfOU8j3I5HxkSS/xA77J3wA==", + "requires": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.15.0" + } + } + } + }, + "rc-tree-select": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.3.3.tgz", + "integrity": "sha512-0tilOHLJA6p+TNg4kD559XnDX3PTEYuoSF7m7ryzFLAYvdEEPtjn0QZc5z6L0sMKBiBlj8a2kf0auw8XyHU3lA==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "^12.0.0", + "rc-tree": "^4.0.0", + "rc-util": "^5.0.5" + } + }, "rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", + "version": "5.24.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", "requires": { + "@babel/runtime": "^7.18.3", "react-is": "^16.12.0", "shallowequal": "^1.1.0" } @@ -78671,9 +78835,9 @@ } }, "date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==" + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" }, "date-format": { "version": "0.0.2", @@ -87135,7 +87299,7 @@ "json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", - "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", "requires": { "string-convert": "^0.2.0" } @@ -92513,27 +92677,6 @@ } } }, - "rc-dialog": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.4.5.tgz", - "integrity": "sha512-0a1Uuy1BRBTdIkfR1VE91kis6dBui7tAIPaQQLj28vBdGg9IqVkiLguCdaDW+4E4vZediePz49PKFbLkx2PL5Q==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-motion": "^2.3.0", - "rc-util": "^5.0.1" - } - }, - "rc-drawer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.1.0.tgz", - "integrity": "sha512-kjeQFngPjdzAFahNIV0EvEBoIKMOnvUsAxpkSPELoD/1DuR4nLafom5ryma+TIxGwkFJ92W6yjsMi1U9aiOTeQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-util": "^5.0.1" - } - }, "rc-dropdown": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.0.tgz", @@ -92554,18 +92697,6 @@ "rc-util": "^5.0.0" } }, - "rc-image": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-4.2.0.tgz", - "integrity": "sha512-yGqq6wPrIn86hMfC1Hl7M3NNS6zqnl9dvFWJg/StuI86jZBU0rm9rePTfKs+4uiwU3HXxpfsXlaG2p8GWRDLiw==", - "requires": { - "@ant-design/icons": "^4.2.2", - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-dialog": "~8.4.0", - "rc-util": "^5.0.6" - } - }, "rc-input-number": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-6.1.2.tgz", @@ -92667,37 +92798,6 @@ "classnames": "^2.2.1" } }, - "rc-picker": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.4.3.tgz", - "integrity": "sha512-tOIHslTQKpoGNmbpp6YOBwS39dQSvtAuhOm3bWCkkc4jCqUqeR/velCwqefZX1BX4+t1gUMc1dIia9XvOKrEkg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "date-fns": "^2.15.0", - "dayjs": "^1.8.30", - "moment": "^2.24.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - }, - "dependencies": { - "rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "requires": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, "rc-progress": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.1.tgz", @@ -92728,32 +92828,6 @@ "resize-observer-polyfill": "^1.5.1" } }, - "rc-select": { - "version": "11.5.3", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-11.5.3.tgz", - "integrity": "sha512-ASSO4J/ayfbQQ+KOEounIMGhySDHpQtrIuH1WEABOBy8HgKec8kOLmyLH+YIXSUDnTf/gtxmflgFtl7sQ9pkSw==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1", - "rc-virtual-list": "^3.2.0", - "warning": "^4.0.3" - } - }, - "rc-slider": { - "version": "9.6.5", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.6.5.tgz", - "integrity": "sha512-XRUJDK668hy8MwGnHzZlXCQXXIOUnEs4m2vwk1jgDILVBxI0GwGOlC6T499pYY+NEWg8YgdCOAucFs/+X5WHpg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-tooltip": "^5.0.1", - "rc-util": "^5.0.0", - "shallowequal": "^1.1.0" - } - }, "rc-steps": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.3.tgz", @@ -92774,34 +92848,6 @@ "rc-util": "^5.0.1" } }, - "rc-table": { - "version": "7.11.3", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.11.3.tgz", - "integrity": "sha512-YyZry1CdqUrcH7MmWtLQZVvVZWbmTEbI5m650AZ+zYw4D5VF701samkMYl5z/H9yQFr+ugvDtXcya+e3vwRkMQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-resize-observer": "^0.2.0", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - }, - "dependencies": { - "rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "requires": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, "rc-tabs": { "version": "11.7.2", "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.7.2.tgz", @@ -92842,39 +92888,6 @@ "rc-resize-observer": "^0.2.3" } }, - "rc-tooltip": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.0.1.tgz", - "integrity": "sha512-3AnxhUS0j74xAV3khrKw8o6rg+Ima3nw09DJBezMPnX3ImQUAnayWsPSlN1mEnihjA43rcFkGM1emiKE+CXyMQ==", - "requires": { - "@babel/runtime": "^7.11.2", - "rc-trigger": "^5.0.0" - } - }, - "rc-tree": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.0.0.tgz", - "integrity": "sha512-C2xlkA+/IypkHBPzbpAJGVWJh2HjeRbYCusA/m5k09WT6hQT0nC7LtLVmnb7QZecdBQPhoOgQh8gPwBR+xEMjQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-util": "^5.0.0", - "rc-virtual-list": "^3.0.1" - } - }, - "rc-tree-select": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.2.0.tgz", - "integrity": "sha512-VrrvBiOov6WR44RTGMqSw1Dmodg6Y++EH6a6R0ew43qsV4Ob0FGYRgoX811kImtt2Z+oAPJ6zZXN4WKtsQd3Gw==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-select": "^11.1.1", - "rc-tree": "^4.0.0", - "rc-util": "^5.0.5" - } - }, "rc-trigger": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.0.tgz", @@ -92945,32 +92958,6 @@ } } }, - "rc-virtual-list": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.2.3.tgz", - "integrity": "sha512-uEeYDQWwQhxR97SekPeGRbzPtHSbSpw/mYb6QpZZ9bA43kf7s1socV3fD3ySYhQVzo0I+/IUD9jFGit6FbM0WA==", - "requires": { - "classnames": "^2.2.6", - "rc-resize-observer": "^0.2.3", - "rc-util": "^5.0.7" - }, - "dependencies": { - "rc-util": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.5.1.tgz", - "integrity": "sha512-lnkBptu1RX65GO6jf28scbDMM/9MVl/hYI0uMEVM+cQ0ALLhFChDzgv7ciNpjayCH88wSDHTp6582es4tzJHhA==", - "requires": { - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, "re-resizable": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.6.1.tgz", @@ -94228,9 +94215,9 @@ } }, "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" }, "regenerator-transform": { "version": "0.15.0", @@ -96413,7 +96400,7 @@ "string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" }, "string-hash": { "version": "1.1.3", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index d121f296dc5cd..065e864dcf4b4 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -117,7 +117,7 @@ "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "ace-builds": "^1.4.14", - "antd": "^4.9.4", + "antd": "4.10.3", "array-move": "^2.2.1", "babel-plugin-typescript-to-proptypes": "^2.0.0", "bootstrap": "^3.4.1", diff --git a/superset-frontend/packages/superset-ui-chart-controls/package.json b/superset-frontend/packages/superset-ui-chart-controls/package.json index 93019ad457f50..e4865c0dc149a 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/package.json +++ b/superset-frontend/packages/superset-ui-chart-controls/package.json @@ -39,7 +39,7 @@ "@testing-library/react-hooks": "^5.0.3", "@testing-library/user-event": "^12.7.0", "ace-builds": "^1.4.14", - "antd": "^4.9.4", + "antd": "4.10.3", "brace": "^0.11.1", "memoize-one": "^5.1.1", "react": "^16.13.1", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts index 962d9ac0ab670..7c57a3e1709ad 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts @@ -30,12 +30,6 @@ export * from './components/ColumnOption'; export * from './components/ColumnTypeLabel/ColumnTypeLabel'; export * from './components/MetricOption'; -// React control components -export { default as sharedControls, withDndFallback } from './shared-controls'; -export { default as sharedControlComponents } from './shared-controls/components'; -export { legacySortBy } from './shared-controls/legacySortBy'; -export * from './shared-controls/emitFilterControl'; -export * from './shared-controls/components'; +export * from './shared-controls'; export * from './types'; -export * from './shared-controls/mixins'; export * from './fixtures'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/sortOperator.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/sortOperator.ts index 277d2df559ced..0650c8b577851 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/sortOperator.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/sortOperator.ts @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -17,25 +16,50 @@ * specific language governing permissions and limitationsxw * under the License. */ -import { DTTM_ALIAS, PostProcessingSort, RollingType } from '@superset-ui/core'; +import { isEmpty } from 'lodash'; +import { + ensureIsArray, + getMetricLabel, + getXAxisLabel, + hasGenericChartAxes, + isDefined, + PostProcessingSort, +} from '@superset-ui/core'; import { PostProcessingFactory } from './types'; export const sortOperator: PostProcessingFactory = ( formData, queryObject, ) => { - const { x_axis: xAxis } = formData; + // the sortOperator only used in the barchart v2 + const sortableLabels = [ + getXAxisLabel(formData), + ...ensureIsArray(formData.metrics).map(metric => getMetricLabel(metric)), + ].filter(Boolean); + if ( - (xAxis || queryObject.is_timeseries) && - Object.values(RollingType).includes(formData.rolling_type) + hasGenericChartAxes && + isDefined(formData?.x_axis_sort) && + isDefined(formData?.x_axis_sort_asc) && + sortableLabels.includes(formData.x_axis_sort) && + // the sort operator doesn't support sort-by multiple series. + isEmpty(formData.groupby) ) { - const index = xAxis || DTTM_ALIAS; + if (formData.x_axis_sort === getXAxisLabel(formData)) { + return { + operation: 'sort', + options: { + is_sort_index: true, + ascending: formData.x_axis_sort_asc, + }, + }; + } + return { operation: 'sort', options: { - columns: { - [index]: true, - }, + by: formData.x_axis_sort, + ascending: formData.x_axis_sort_asc, }, }; } diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx index 9d1c52433fb34..296f8d9da820f 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx @@ -16,9 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -import { ContributionType, hasGenericChartAxes, t } from '@superset-ui/core'; -import { ControlPanelSectionConfig } from '../types'; -import { emitFilterControl } from '../shared-controls/emitFilterControl'; +import { hasGenericChartAxes, t } from '@superset-ui/core'; +import { ControlPanelSectionConfig, ControlSetRow } from '../types'; +import { + contributionModeControl, + emitFilterControl, + xAxisSortControl, + xAxisSortAscControl, +} from '../shared-controls'; + +const controlsWithoutXAxis: ControlSetRow[] = [ + ['metrics'], + ['groupby'], + [contributionModeControl], + ['adhoc_filters'], + emitFilterControl, + ['limit'], + ['timeseries_limit_metric'], + ['order_desc'], + ['row_limit'], + ['truncate_metric'], + ['show_empty_columns'], +]; export const echartsTimeSeriesQuery: ControlPanelSectionConfig = { label: t('Query'), @@ -26,31 +45,18 @@ export const echartsTimeSeriesQuery: ControlPanelSectionConfig = { controlSetRows: [ [hasGenericChartAxes ? 'x_axis' : null], [hasGenericChartAxes ? 'time_grain_sqla' : null], - ['metrics'], - ['groupby'], - [ - { - name: 'contributionMode', - config: { - type: 'SelectControl', - label: t('Contribution Mode'), - default: null, - choices: [ - [null, t('None')], - [ContributionType.Row, t('Row')], - [ContributionType.Column, t('Series')], - ], - description: t('Calculate contribution per series or row'), - }, - }, - ], - ['adhoc_filters'], - emitFilterControl, - ['limit'], - ['timeseries_limit_metric'], - ['order_desc'], - ['row_limit'], - ['truncate_metric'], - ['show_empty_columns'], + ...controlsWithoutXAxis, + ], +}; + +export const echartsTimeSeriesQueryWithXAxisSort: ControlPanelSectionConfig = { + label: t('Query'), + expanded: true, + controlSetRows: [ + [hasGenericChartAxes ? 'x_axis' : null], + [hasGenericChartAxes ? 'time_grain_sqla' : null], + [hasGenericChartAxes ? xAxisSortControl : null], + [hasGenericChartAxes ? xAxisSortAscControl : null], + ...controlsWithoutXAxis, ], }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx new file mode 100644 index 0000000000000..5ece20ac06001 --- /dev/null +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx @@ -0,0 +1,141 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + ContributionType, + ensureIsArray, + FeatureFlag, + getColumnLabel, + getMetricLabel, + isDefined, + isEqualArray, + isFeatureEnabled, + QueryFormColumn, + QueryFormMetric, + t, +} from '@superset-ui/core'; +import { ControlPanelState, ControlState, ControlStateMapping } from '../types'; +import { isTemporalColumn } from '../utils'; + +export const emitFilterControl = isFeatureEnabled( + FeatureFlag.DASHBOARD_CROSS_FILTERS, +) + ? [ + { + name: 'emit_filter', + config: { + type: 'CheckboxControl', + label: t('Enable dashboard cross filters'), + default: false, + renderTrigger: true, + description: t('Enable dashboard cross filters'), + }, + }, + ] + : []; + +export const contributionModeControl = { + name: 'contributionMode', + config: { + type: 'SelectControl', + label: t('Contribution Mode'), + default: null, + choices: [ + [null, t('None')], + [ContributionType.Row, t('Row')], + [ContributionType.Column, t('Series')], + ], + description: t('Calculate contribution per series or row'), + }, +}; + +const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) => + isDefined(controls?.x_axis?.value) && + !isTemporalColumn( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + ) && + Array.isArray(controls?.groupby?.value) && + controls.groupby.value.length === 0; + +export const xAxisSortControl = { + name: 'x_axis_sort', + config: { + type: 'XAxisSortControl', + label: t('X-Axis Sort By'), + description: t('Whether to sort descending or ascending on the X-Axis.'), + shouldMapStateToProps: ( + prevState: ControlPanelState, + state: ControlPanelState, + ) => { + const prevOptions = [ + getColumnLabel(prevState?.controls?.x_axis?.value as QueryFormColumn), + ...ensureIsArray(prevState?.controls?.metrics?.value).map(metric => + getMetricLabel(metric as QueryFormMetric), + ), + ]; + const currOptions = [ + getColumnLabel(state?.controls?.x_axis?.value as QueryFormColumn), + ...ensureIsArray(state?.controls?.metrics?.value).map(metric => + getMetricLabel(metric as QueryFormMetric), + ), + ]; + return !isEqualArray(prevOptions, currOptions); + }, + mapStateToProps: ( + { controls }: { controls: ControlStateMapping }, + controlState: ControlState, + ) => { + const choices = [ + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + ...ensureIsArray(controls?.metrics?.value).map(metric => + getMetricLabel(metric as QueryFormMetric), + ), + ].filter(Boolean); + const shouldReset = !( + typeof controlState.value === 'string' && + choices.includes(controlState.value) && + !isTemporalColumn( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + ) + ); + + return { + shouldReset, + options: choices.map(entry => ({ + value: entry, + label: entry, + })), + }; + }, + visibility: xAxisSortVisibility, + }, +}; + +export const xAxisSortAscControl = { + name: 'x_axis_sort_asc', + config: { + type: 'CheckboxControl', + label: t('X-Axis Sort Ascending'), + default: true, + description: t('Whether to sort descending or ascending on the X-Axis.'), + visibility: xAxisSortVisibility, + }, +}; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/legacySortBy.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.ts similarity index 64% rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/legacySortBy.tsx rename to superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.ts index 3cb882d29ece6..acf3f3e8fdd51 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/legacySortBy.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.ts @@ -16,22 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { t } from '@superset-ui/core'; -import { ControlSetRow } from '../types'; - -export const legacySortBy: ControlSetRow[] = [ - ['legacy_order_by'], - [ - { - name: 'order_desc', - config: { - type: 'CheckboxControl', - label: t('Sort descending'), - default: true, - description: t( - 'Whether to sort descending or ascending. Takes effect only when "Sort by" is set', - ), - }, - }, - ], -]; +export { default as sharedControls } from './sharedControls'; +export { withDndFallback } from './dndControls'; +// React control components +export { default as sharedControlComponents } from './components'; +export * from './components'; +export * from './customControls'; +export * from './mixins'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx similarity index 99% rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx rename to superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx index de94c4e16c92c..11c4a48490398 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx @@ -354,7 +354,7 @@ const show_empty_columns: SharedControlConfig<'CheckboxControl'> = { description: t('Show empty columns'), }; -const datetime_columns_lookup: SharedControlConfig<'HiddenControl'> = { +const temporal_columns_lookup: SharedControlConfig<'HiddenControl'> = { type: 'HiddenControl', initialValue: (control: ControlState, state: ControlPanelState | null) => Object.fromEntries( @@ -400,5 +400,5 @@ export default { truncate_metric, x_axis: dndXAxisControl, show_empty_columns, - datetime_columns_lookup, + temporal_columns_lookup, }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index c2ec315274d8a..60cda2ede8f49 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -30,8 +30,7 @@ import type { QueryFormMetric, QueryResponse, } from '@superset-ui/core'; -import sharedControls from './shared-controls'; -import sharedControlComponents from './shared-controls/components'; +import { sharedControls, sharedControlComponents } from './shared-controls'; export type { Metric } from '@superset-ui/core'; export type { ControlFormItemSpec } from './components/ControlForm'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx index 161dd5ad07aae..73b4133f990dc 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx @@ -17,8 +17,7 @@ * under the License. */ import React, { ReactElement } from 'react'; -import sharedControls from '../shared-controls'; -import sharedControlComponents from '../shared-controls/components'; +import { sharedControls, sharedControlComponents } from '../shared-controls'; import { ControlType, ControlSetItem, diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/sortOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/sortOperator.test.ts index 6f0267d91305e..750d726c902af 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/sortOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/sortOperator.test.ts @@ -18,6 +18,7 @@ */ import { QueryObject, SqlaFormData } from '@superset-ui/core'; import { sortOperator } from '@superset-ui/chart-controls'; +import * as supersetCoreModule from '@superset-ui/core'; const formData: SqlaFormData = { metrics: [ @@ -52,92 +53,96 @@ const queryObject: QueryObject = { ], }; -test('skip sort', () => { +test('should ignore the sortOperator', () => { + // FF is disabled + Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', { + value: false, + }); expect(sortOperator(formData, queryObject)).toEqual(undefined); - expect( - sortOperator(formData, { ...queryObject, is_timeseries: false }), - ).toEqual(undefined); + + // FF is enabled + Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', { + value: true, + }); expect( sortOperator( - { ...formData, rolling_type: 'xxxx' }, - { ...queryObject, is_timeseries: true }, + { + ...formData, + ...{ + x_axis_sort: undefined, + x_axis_sort_asc: true, + }, + }, + queryObject, ), ).toEqual(undefined); - expect( - sortOperator(formData, { ...queryObject, is_timeseries: true }), - ).toEqual(undefined); -}); -test('sort by __timestamp', () => { - expect( - sortOperator( - { ...formData, rolling_type: 'cumsum' }, - { ...queryObject, is_timeseries: true }, - ), - ).toEqual({ - operation: 'sort', - options: { - columns: { - __timestamp: true, - }, - }, + // sortOperator doesn't support multiple series + Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', { + value: true, }); - expect( sortOperator( - { ...formData, rolling_type: 'sum' }, - { ...queryObject, is_timeseries: true }, - ), - ).toEqual({ - operation: 'sort', - options: { - columns: { - __timestamp: true, + { + ...formData, + ...{ + x_axis_sort: 'metric label', + x_axis_sort_asc: true, + groupby: ['col1'], + x_axis: 'axis column', + }, }, - }, - }); - - expect( - sortOperator( - { ...formData, rolling_type: 'mean' }, - { ...queryObject, is_timeseries: true }, + queryObject, ), - ).toEqual({ - operation: 'sort', - options: { - columns: { - __timestamp: true, - }, - }, - }); + ).toEqual(undefined); +}); +test('should sort by metric', () => { + Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', { + value: true, + }); expect( sortOperator( - { ...formData, rolling_type: 'std' }, - { ...queryObject, is_timeseries: true }, + { + ...formData, + ...{ + metrics: ['a metric label'], + x_axis_sort: 'a metric label', + x_axis_sort_asc: true, + }, + }, + queryObject, ), ).toEqual({ operation: 'sort', options: { - columns: { - __timestamp: true, - }, + by: 'a metric label', + ascending: true, }, }); }); -test('sort by named x-axis', () => { +test('should sort by axis', () => { + Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', { + value: true, + }); expect( sortOperator( - { ...formData, x_axis: 'ds', rolling_type: 'cumsum' }, - { ...queryObject }, + { + ...formData, + ...{ + x_axis_sort: 'Categorical Column', + x_axis_sort_asc: true, + x_axis: 'Categorical Column', + }, + }, + queryObject, ), ).toEqual({ operation: 'sort', options: { - columns: { - ds: true, - }, + is_sort_index: true, + ascending: true, }, }); }); diff --git a/superset-frontend/packages/superset-ui-core/src/query/getColumnLabel.ts b/superset-frontend/packages/superset-ui-core/src/query/getColumnLabel.ts index f449a44cb2702..f1b4e6ffa7308 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/getColumnLabel.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/getColumnLabel.ts @@ -23,8 +23,8 @@ export default function getColumnLabel(column: QueryFormColumn): string { if (isPhysicalColumn(column)) { return column; } - if (column.label) { + if (column?.label) { return column.label; } - return column.sqlExpression; + return column?.sqlExpression; } diff --git a/superset-frontend/packages/superset-ui-core/src/query/index.ts b/superset-frontend/packages/superset-ui-core/src/query/index.ts index 3ea6dad75f589..bb83e3d340fd2 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/index.ts @@ -29,13 +29,8 @@ export { default as getMetricLabel } from './getMetricLabel'; export { default as DatasourceKey } from './DatasourceKey'; export { default as normalizeOrderBy } from './normalizeOrderBy'; export { normalizeTimeColumn } from './normalizeTimeColumn'; -export { - getXAxisLabel, - getXAxisColumn, - isXAxisSet, - hasGenericChartAxes, -} from './getXAxis'; export { default as extractQueryFields } from './extractQueryFields'; +export * from './getXAxis'; export * from './types/AnnotationLayer'; export * from './types/QueryFormData'; diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Dashboard.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Dashboard.ts index c86c1cfbe41a3..b7ac546993783 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Dashboard.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Dashboard.ts @@ -91,6 +91,8 @@ export type Filter = { description: string; }; +export type FilterWithDataMask = Filter & { dataMask?: DataMaskWithId }; + export type Divider = Partial> & { id: string; title: string; diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/PostProcessing.ts b/superset-frontend/packages/superset-ui-core/src/query/types/PostProcessing.ts index 7b63ea056a7fa..b88dafb5c7930 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/PostProcessing.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/PostProcessing.ts @@ -182,7 +182,9 @@ export type PostProcessingCompare = interface _PostProcessingSort { operation: 'sort'; options: { - columns: Record; + is_sort_index?: boolean; + by?: string[] | string; + ascending?: boolean[] | boolean; }; } export type PostProcessingSort = _PostProcessingSort | DefaultPostProcessing; diff --git a/superset-frontend/packages/superset-ui-core/src/style/index.tsx b/superset-frontend/packages/superset-ui-core/src/style/index.tsx index b20dbc5aa9f5e..1a4d9031f8f91 100644 --- a/superset-frontend/packages/superset-ui-core/src/style/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/style/index.tsx @@ -22,6 +22,7 @@ import createCache from '@emotion/cache'; export { css, + keyframes, jsx, ThemeProvider, CacheProvider as EmotionCacheProvider, diff --git a/superset-frontend/packages/superset-ui-core/test/__mocks__/resize-observer-polyfill.ts b/superset-frontend/packages/superset-ui-core/test/__mocks__/resize-observer-polyfill.ts index 238e14ab23360..719ad1119f1f4 100644 --- a/superset-frontend/packages/superset-ui-core/test/__mocks__/resize-observer-polyfill.ts +++ b/superset-frontend/packages/superset-ui-core/test/__mocks__/resize-observer-polyfill.ts @@ -37,6 +37,11 @@ export default function ResizeObserver(callback: ObserveCallback) { allCallbacks.push(callback); } }, + unobserve() { + if (callback) { + allCallbacks.splice(allCallbacks.indexOf(callback), 1); + } + }, }; } diff --git a/superset-frontend/packages/superset-ui-core/test/query/types/PostProcessing.test.ts b/superset-frontend/packages/superset-ui-core/test/query/types/PostProcessing.test.ts index 9cdec11ec8cba..db95137409a28 100644 --- a/superset-frontend/packages/superset-ui-core/test/query/types/PostProcessing.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/query/types/PostProcessing.test.ts @@ -147,7 +147,7 @@ const ROLLING_RULE: PostProcessingRolling = { const SORT_RULE: PostProcessingSort = { operation: 'sort', options: { - columns: { foo: true }, + by: 'foo', }, }; diff --git a/superset-frontend/packages/superset-ui-demo/package.json b/superset-frontend/packages/superset-ui-demo/package.json index bf3da61c12583..a2ff398662dc1 100644 --- a/superset-frontend/packages/superset-ui-demo/package.json +++ b/superset-frontend/packages/superset-ui-demo/package.json @@ -41,7 +41,7 @@ "@storybook/addons": "^6.3.12", "@storybook/react": "^6.3.12", "@types/react-loadable": "^5.5.3", - "antd": "^4.9.4", + "antd": "4.10.3", "bootstrap": "^3.4.1", "core-js": "3.8.3", "gh-pages": "^3.0.0", diff --git a/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb b/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb index e5c200f4c0235..4908a5e6d4571 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb +++ b/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb @@ -648,6 +648,7 @@ " 'japan',\n", " 'kenya',\n", " 'korea',\n", + " 'latvia',\n", " 'liechtenstein',\n", " 'malaysia',\n", " 'mexico',\n", diff --git a/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts b/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts index 81cc2080e249f..a083a744392f8 100755 --- a/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts +++ b/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts @@ -56,6 +56,7 @@ import jordan from './countries/jordan.geojson'; import kenya from './countries/kenya.geojson'; import korea from './countries/korea.geojson'; import kuwait from './countries/kuwait.geojson'; +import latvia from './countries/latvia.geojson'; import liechtenstein from './countries/liechtenstein.geojson'; import lithuania from './countries/lithuania.geojson'; import nigeria from './countries/nigeria.geojson'; @@ -141,6 +142,7 @@ export const countries = { kenya, korea, kuwait, + latvia, liechtenstein, lithuania, malaysia, diff --git a/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries/latvia.geojson b/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries/latvia.geojson new file mode 100644 index 0000000000000..90dc6ddd50c4c --- /dev/null +++ b/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries/latvia.geojson @@ -0,0 +1,125 @@ +{ +"type": "FeatureCollection", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "ISO": "LV-084", "NAME_1": "Rujienas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.263501424000111, 58.075138449000079 ], [ 25.281691528000067, 58.073407288000013 ], [ 25.299571574000112, 58.065604147 ], [ 25.30639286300007, 58.058731181000027 ], [ 25.318278443000111, 58.040618592 ], [ 25.324582967000111, 58.034753317000096 ], [ 25.333678019000104, 58.03180776 ], [ 25.33987919100008, 58.03258290600003 ], [ 25.346390421000137, 58.034443258000024 ], [ 25.356519002000084, 58.034753317000096 ], [ 25.396619914000098, 58.02317779600007 ], [ 25.419731367148021, 58.008250005422155 ], [ 25.406834255496449, 57.991310424136714 ], [ 25.403702900668293, 57.97095661685438 ], [ 25.370823673173959, 57.969390939440302 ], [ 25.345772833649335, 57.966259584612089 ], [ 25.341075801407101, 57.985047714480402 ], [ 25.326984703781022, 57.991310424136714 ], [ 25.301933864256398, 57.97095661685438 ], [ 25.278448702145852, 57.953734165299466 ], [ 25.261226250590994, 57.938077390259309 ], [ 25.270620315075462, 57.913026550734685 ], [ 25.30036818684232, 57.905198163664295 ], [ 25.336378769164867, 57.903632486250217 ], [ 25.358298253861335, 57.881713001553749 ], [ 25.381783415971825, 57.883278678967827 ], [ 25.406834255496449, 57.847268096645337 ], [ 25.417793997395052, 57.815954547464344 ], [ 25.452238902303463, 57.815954547464344 ], [ 25.455370257131619, 57.789338030525641 ], [ 25.378652061143669, 57.787772353111563 ], [ 25.31289360705432, 57.78307532086933 ], [ 25.270620315075462, 57.778378287727776 ], [ 25.267488960247306, 57.79560074018201 ], [ 25.245569475550838, 57.800297772424244 ], [ 25.250266507793071, 57.820651579706634 ], [ 25.247135152964916, 57.851965128887571 ], [ 25.217387281198057, 57.872318936169961 ], [ 25.212690248955823, 57.887975711210061 ], [ 25.195467796501589, 57.905198163664295 ], [ 25.167285602148809, 57.914592228148763 ], [ 25.134406374654475, 57.930249003188919 ], [ 25.12501231016995, 57.95060281047131 ], [ 25.137537729482631, 57.956865520127621 ], [ 25.13284069724034, 57.977219327410012 ], [ 25.10465850288756, 57.981916359652246 ], [ 25.095264438403092, 58.003835844348714 ], [ 25.10465850288756, 58.025755329045182 ], [ 25.109355535129851, 58.044543458913495 ], [ 25.094209432000127, 58.067309469000051 ], [ 25.141028280000057, 58.068007101000049 ], [ 25.166969848000093, 58.058731181000027 ], [ 25.180715780000128, 58.038163961000052 ], [ 25.189914184000116, 58.013514303000036 ], [ 25.20210982200004, 57.991965230000076 ], [ 25.216785930000128, 57.985376486000106 ], [ 25.232495565000107, 57.985376486000106 ], [ 25.264534953000094, 57.994187318000101 ], [ 25.284378703000101, 58.008139954000072 ], [ 25.277557414000057, 58.024107972000124 ], [ 25.249548788000141, 58.051186422000072 ], [ 25.250685669000035, 58.068498027 ], [ 25.263501424000111, 58.075138449000079 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-060", "NAME_1": "Mazsalacas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.034856819000083, 58.048574085000027 ], [ 25.048837524000106, 58.056199036000052 ], [ 25.070644978000132, 58.063614604000051 ], [ 25.094209432000127, 58.067309469000051 ], [ 25.109355535129851, 58.044543458913495 ], [ 25.10465850288756, 58.025755329045182 ], [ 25.095264438403092, 58.003835844348714 ], [ 25.10465850288756, 57.981916359652246 ], [ 25.13284069724034, 57.977219327410012 ], [ 25.137537729482631, 57.956865520127621 ], [ 25.12501231016995, 57.95060281047131 ], [ 25.134406374654475, 57.930249003188919 ], [ 25.167285602148809, 57.914592228148763 ], [ 25.195467796501589, 57.905198163664295 ], [ 25.212690248955823, 57.887975711210061 ], [ 25.217387281198057, 57.872318936169961 ], [ 25.247135152964916, 57.851965128887571 ], [ 25.250266507793071, 57.820651579706634 ], [ 25.245569475550838, 57.800297772424244 ], [ 25.223649990854369, 57.804994804666478 ], [ 25.157891536764964, 57.833176999918578 ], [ 25.142234762624184, 57.844136741817181 ], [ 25.112486889958006, 57.837874032160812 ], [ 25.123446632755872, 57.826914289362946 ], [ 25.13284069724034, 57.808126160393954 ], [ 25.128143664998106, 57.798732095010166 ], [ 25.112486889958006, 57.800297772424244 ], [ 25.092133083574936, 57.812823192636188 ], [ 25.06081953349468, 57.812823192636188 ], [ 25.034203016555978, 57.814388870050266 ], [ 25.017469133393774, 57.796470091421554 ], [ 25.007445510202103, 57.801381741474188 ], [ 24.965690951676038, 57.808306382733349 ], [ 24.953288609082051, 57.800089830281422 ], [ 24.946002231717614, 57.817530626437247 ], [ 24.941971470307067, 57.818615831155682 ], [ 24.938250767259092, 57.819055080727367 ], [ 24.933134800230846, 57.81755646395959 ], [ 24.926158482128244, 57.817091375966186 ], [ 24.909363641119114, 57.820657050282591 ], [ 24.901147087767868, 57.825514634892443 ], [ 24.895721063276426, 57.831199041802336 ], [ 24.893395623309345, 57.843368842198288 ], [ 24.887814569187015, 57.858535875230416 ], [ 24.885179070857419, 57.87153249792641 ], [ 24.883473747615369, 57.883753974266483 ], [ 24.885024041226529, 57.889076646869739 ], [ 24.881923455802848, 57.893004055492725 ], [ 24.873396844089086, 57.898016668834146 ], [ 24.864043410075283, 57.900316271278882 ], [ 24.842339307612917, 57.900936388003856 ], [ 24.82761152505185, 57.980130520107366 ], [ 24.827417479000076, 57.981876936000091 ], [ 24.848332967000118, 57.992146098000049 ], [ 24.872517537000078, 58.000440165 ], [ 24.949308716000047, 58.006434631000033 ], [ 24.9748368730001, 58.015839742000068 ], [ 25.034856819000083, 58.048574085000027 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-005", "NAME_1": "Alojas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.816810344000146, 57.976669007000041 ], [ 24.827417479000076, 57.981876936000091 ], [ 24.82761152505185, 57.980130520107366 ], [ 24.842339307612917, 57.900936388003856 ], [ 24.864043410075283, 57.900316271278882 ], [ 24.873396844089086, 57.898016668834146 ], [ 24.881923455802848, 57.893004055492725 ], [ 24.885024041226529, 57.889076646869739 ], [ 24.883473747615369, 57.883753974266483 ], [ 24.885179070857419, 57.87153249792641 ], [ 24.887814569187015, 57.858535875230416 ], [ 24.893395623309345, 57.843368842198288 ], [ 24.895721063276426, 57.831199041802336 ], [ 24.901147087767868, 57.825514634892443 ], [ 24.909363641119114, 57.820657050282591 ], [ 24.926158482128244, 57.817091375966186 ], [ 24.933134800230846, 57.81755646395959 ], [ 24.938250767259092, 57.819055080727367 ], [ 24.941971470307067, 57.818615831155682 ], [ 24.946002231717614, 57.817530626437247 ], [ 24.953288609082051, 57.800089830281422 ], [ 24.965690951676038, 57.808306382733349 ], [ 25.007445510202103, 57.801381741474188 ], [ 25.017469133393774, 57.796470091421554 ], [ 25.077260369870146, 57.767171942730954 ], [ 25.083616571247774, 57.75414948251256 ], [ 25.098706089014797, 57.736011054366656 ], [ 25.090902947712834, 57.73125682254431 ], [ 25.087182244664859, 57.727174384290379 ], [ 25.080205925662938, 57.721231594062772 ], [ 25.07617516425239, 57.714513658378621 ], [ 25.075710077158305, 57.705935370720738 ], [ 25.082221307267503, 57.696891995069507 ], [ 25.092453241323994, 57.688236192146519 ], [ 25.085321892691184, 57.674619452725551 ], [ 25.093745150718121, 57.653122056737516 ], [ 25.067028435813654, 57.654982407811815 ], [ 25.062222528047187, 57.65239858722498 ], [ 25.063307732765622, 57.648109443396095 ], [ 25.075865105889932, 57.638704332538907 ], [ 25.083306511985938, 57.63410513034745 ], [ 25.084701775966153, 57.629764308775805 ], [ 25.067803582169574, 57.626457017777113 ], [ 25.061602411322212, 57.623950711556063 ], [ 25.042378777558497, 57.625836900152819 ], [ 25.03302534444407, 57.630177720825145 ], [ 25.027599318153989, 57.634260159079076 ], [ 25.024033643837583, 57.637360745402077 ], [ 25.019072707339546, 57.638135890858621 ], [ 25.005740186960054, 57.636120510153376 ], [ 25.000159132837666, 57.635862127734981 ], [ 24.991477492392335, 57.636482246258652 ], [ 24.966311069300389, 57.628420722538294 ], [ 24.93453006421106, 57.612039293578505 ], [ 24.881437081755905, 57.624673657663152 ], [ 24.892562524640766, 57.652487264875333 ], [ 24.831372589673265, 57.666394068031764 ], [ 24.824419188095021, 57.699770395787084 ], [ 24.828591229401695, 57.719239921285293 ], [ 24.799386942053729, 57.733146724441724 ], [ 24.747931768935985, 57.731756044305996 ], [ 24.707602039602421, 57.749834888769101 ], [ 24.692304555410885, 57.760960331653962 ], [ 24.649193464906489, 57.770695093503718 ], [ 24.597738292688064, 57.870824078568319 ], [ 24.5740967267825, 57.881949520553917 ], [ 24.59078489110982, 57.894465643574563 ], [ 24.588003529938931, 57.920888570650959 ], [ 24.582445250259351, 57.946010847151072 ], [ 24.624057251000096, 57.943957825000055 ], [ 24.684828735000082, 57.94757517500004 ], [ 24.700435018000121, 57.954189758000027 ], [ 24.718831828000134, 57.981836650000034 ], [ 24.733507935000119, 57.992197774000047 ], [ 24.764720500000095, 57.987908631 ], [ 24.790765421000089, 57.978994446000073 ], [ 24.816810344000146, 57.976669007000041 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-064", "NAME_1": "Nauksenu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.551815984000143, 57.949859510000024 ], [ 25.552114298000049, 57.948815410000051 ], [ 25.552941122000078, 57.942174988000048 ], [ 25.55831547000011, 57.934242656000023 ], [ 25.579399455000072, 57.919463196 ], [ 25.601516968000055, 57.912202658000112 ], [ 25.625081421000061, 57.910290630000091 ], [ 25.650092814000061, 57.911427511000014 ], [ 25.703799772000053, 57.921326524000065 ], [ 25.666077060707778, 57.914373651522794 ], [ 25.660474006470224, 57.892672743452295 ], [ 25.657342650742692, 57.878581646725593 ], [ 25.65336801961837, 57.863651497818353 ], [ 25.613503682249132, 57.866056226513592 ], [ 25.582810907197597, 57.883133857541452 ], [ 25.574129265852946, 57.880834255996092 ], [ 25.565912713400962, 57.879929918430946 ], [ 25.5566109562306, 57.880033271218451 ], [ 25.546534050905734, 57.876881008951386 ], [ 25.535010206555796, 57.87440054025268 ], [ 25.522297803800598, 57.86887116207447 ], [ 25.501213819861903, 57.868225206028399 ], [ 25.469461354757698, 57.873884613584039 ], [ 25.466329999030222, 57.85979351685728 ], [ 25.511135694656559, 57.840991726287086 ], [ 25.526018507747892, 57.829958808352274 ], [ 25.542348259864298, 57.820657050282591 ], [ 25.555370720981955, 57.816057848091191 ], [ 25.560176628748422, 57.807582913220813 ], [ 25.555060662619439, 57.799573066343896 ], [ 25.552890252283305, 57.792390041766964 ], [ 25.494512194282322, 57.789338030525641 ], [ 25.455370257131619, 57.789338030525641 ], [ 25.452238902303463, 57.815954547464344 ], [ 25.417793997395052, 57.815954547464344 ], [ 25.406834255496449, 57.847268096645337 ], [ 25.381783415971825, 57.883278678967827 ], [ 25.358298253861335, 57.881713001553749 ], [ 25.336378769164867, 57.903632486250217 ], [ 25.30036818684232, 57.905198163664295 ], [ 25.270620315075462, 57.913026550734685 ], [ 25.261226250590994, 57.938077390259309 ], [ 25.278448702145852, 57.953734165299466 ], [ 25.301933864256398, 57.97095661685438 ], [ 25.326984703781022, 57.991310424136714 ], [ 25.341075801407101, 57.985047714480402 ], [ 25.345772833649335, 57.966259584612089 ], [ 25.370823673173959, 57.969390939440302 ], [ 25.403702900668293, 57.97095661685438 ], [ 25.406834255496449, 57.991310424136714 ], [ 25.419731367148021, 58.008250005422155 ], [ 25.457184692000055, 57.984058736000051 ], [ 25.496613810000071, 57.970597026000078 ], [ 25.531960489000056, 57.966437073000051 ], [ 25.541675659000106, 57.962819723000067 ], [ 25.550253947000044, 57.955326640000024 ], [ 25.551815984000143, 57.949859510000024 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-085", "NAME_1": "Salacgrivas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.553880656000103, 57.947420146000084 ], [ 24.582445250259351, 57.946010847151072 ], [ 24.588003529938931, 57.920888570650959 ], [ 24.59078489110982, 57.894465643574563 ], [ 24.5740967267825, 57.881949520553917 ], [ 24.597738292688064, 57.870824078568319 ], [ 24.649193464906489, 57.770695093503718 ], [ 24.599128972823792, 57.752616249040614 ], [ 24.608863735572925, 57.715067879978676 ], [ 24.562971283897639, 57.717849240250189 ], [ 24.558799242590965, 57.688644953801543 ], [ 24.564361964033424, 57.673347469610007 ], [ 24.54072039812786, 57.666394068031764 ], [ 24.515688152086511, 57.678910191052466 ], [ 24.4183405281928, 57.670566109338438 ], [ 24.426684610806149, 57.620501616356478 ], [ 24.423903249635259, 57.589906648872727 ], [ 24.43502869252012, 57.588515968736942 ], [ 24.433638012384336, 57.567655764002268 ], [ 24.460060938561469, 57.564874402831379 ], [ 24.478139783024574, 57.567655764002268 ], [ 24.497609307623406, 57.555139640981622 ], [ 24.49065590604522, 57.544014198096761 ], [ 24.553236521148506, 57.541232836925872 ], [ 24.529594956142319, 57.521763312326982 ], [ 24.549064480741151, 57.489777664707447 ], [ 24.565752644169152, 57.474480181415231 ], [ 24.578268768089174, 57.464745418666098 ], [ 24.558799242590965, 57.452229295645452 ], [ 24.511516111679214, 57.448057254338778 ], [ 24.501781348930081, 57.427197049604104 ], [ 24.486483864738545, 57.416071606719242 ], [ 24.447544815540766, 57.407727525005271 ], [ 24.394893257192049, 57.400012509164952 ], [ 24.381602410000085, 57.469427802000041 ], [ 24.379893425000091, 57.506537177000041 ], [ 24.375173373000052, 57.528550523000035 ], [ 24.364512566000087, 57.538397528000075 ], [ 24.358571811000047, 57.550360419000071 ], [ 24.36296634200005, 57.576605536000045 ], [ 24.37476647200009, 57.613470770000049 ], [ 24.36304772200009, 57.672267971000053 ], [ 24.357676629000082, 57.685492255000042 ], [ 24.313731316000087, 57.725043036000045 ], [ 24.299571160000085, 57.743841864000046 ], [ 24.299571160000085, 57.761908270000049 ], [ 24.28882897200009, 57.80890534100007 ], [ 24.285980665000068, 57.832586981000077 ], [ 24.28882897200009, 57.845282294000071 ], [ 24.296153191000087, 57.857367255000042 ], [ 24.306158734238295, 57.868186255842957 ], [ 24.349655395000127, 57.858640036000068 ], [ 24.378180786000087, 57.859441020000091 ], [ 24.401538533000121, 57.866469014000089 ], [ 24.412700643000051, 57.876571757000036 ], [ 24.429133748000112, 57.900342916000099 ], [ 24.441639445000106, 57.908094381000083 ], [ 24.481326945000092, 57.91930816700004 ], [ 24.533830200000068, 57.945068868000035 ], [ 24.553880656000103, 57.947420146000084 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-009", "NAME_1": "Apes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.430678413000066, 57.561764252 ], [ 26.430923706000016, 57.561707255000087 ], [ 26.447563517000049, 57.552922262 ], [ 26.481049846000133, 57.527135722000097 ], [ 26.499550008000057, 57.515818583000012 ], [ 26.515052938000082, 57.517265524000024 ], [ 26.528695516000084, 57.523880107000096 ], [ 26.542234741000073, 57.528169251000079 ], [ 26.551743205000065, 57.526825664000015 ], [ 26.570243367000074, 57.519590963000056 ], [ 26.579855183000092, 57.517833965000065 ], [ 26.590087118000071, 57.520366109000051 ], [ 26.594841349000063, 57.526489767000029 ], [ 26.598355347000108, 57.53385365900003 ], [ 26.604763224000067, 57.540313213000061 ], [ 26.634322144000095, 57.554420879000091 ], [ 26.666981649000064, 57.564859518000034 ], [ 26.700467976000141, 57.570750631000053 ], [ 26.778085978000064, 57.568476868000019 ], [ 26.795759318000137, 57.571319071000104 ], [ 26.806003385248005, 57.55384266750508 ], [ 26.808281793370043, 57.51966654837247 ], [ 26.808281793370043, 57.503717693316844 ], [ 26.758156820081183, 57.503717693316844 ], [ 26.721702292826535, 57.490047245483936 ], [ 26.714867069359741, 57.464984758839535 ], [ 26.703475029648871, 57.423973416240131 ], [ 26.687526174593245, 57.417138192773336 ], [ 26.644236424771179, 57.417138192773336 ], [ 26.614617121882645, 57.419416600895374 ], [ 26.594222040214959, 57.391303412422531 ], [ 26.567712029986183, 57.397427069803427 ], [ 26.544405958867912, 57.395618395572455 ], [ 26.531073439387683, 57.398874009727763 ], [ 26.525802442728548, 57.401871243263258 ], [ 26.52022138770684, 57.406470445454659 ], [ 26.475831332649875, 57.405075182373764 ], [ 26.480637241315605, 57.387401842221209 ], [ 26.479241978234711, 57.382389227980468 ], [ 26.474746127931439, 57.375051175571286 ], [ 26.446840854621712, 57.369160061287744 ], [ 26.43314659993564, 57.361408595929902 ], [ 26.421054314804792, 57.357946275300321 ], [ 26.397024774173985, 57.35427724909573 ], [ 26.393924187850985, 57.350711574779325 ], [ 26.394699334206905, 57.347145901362239 ], [ 26.399040154879231, 57.344019477516838 ], [ 26.404466180269992, 57.342236639909004 ], [ 26.411132440010078, 57.342856757533298 ], [ 26.424154901127793, 57.346990871731293 ], [ 26.430046013612639, 57.34600901980042 ], [ 26.430046013612639, 57.339704495266233 ], [ 26.423534784402761, 57.332159736382721 ], [ 26.359456007588676, 57.322547919051203 ], [ 26.276153598809515, 57.331022853921525 ], [ 26.277548861890466, 57.364715887827913 ], [ 26.286230503235117, 57.365826930968069 ], [ 26.302043491413997, 57.374844469096956 ], [ 26.301423373789646, 57.379831244915977 ], [ 26.299097934721885, 57.389468898870575 ], [ 26.302818637769917, 57.396135159509981 ], [ 26.309794955872519, 57.402207140047437 ], [ 26.312740512564574, 57.409726061408605 ], [ 26.315065951632334, 57.418097643491421 ], [ 26.320491977922416, 57.423782050401257 ], [ 26.318941685210575, 57.429001370217009 ], [ 26.315065951632334, 57.433135484415061 ], [ 26.308554722422457, 57.436158556372277 ], [ 26.300803257064615, 57.438974920955786 ], [ 26.293516879700235, 57.442695624003818 ], [ 26.296152378029831, 57.450007839789919 ], [ 26.304523960112647, 57.455356349915576 ], [ 26.312740512564574, 57.458663641813587 ], [ 26.325969679257298, 57.467603665576689 ], [ 26.311035191121164, 57.469774075013504 ], [ 26.311500278215249, 57.499332993822577 ], [ 26.361781446656437, 57.512820542933696 ], [ 26.362866652274192, 57.515636909315901 ], [ 26.362556593911677, 57.519848537879682 ], [ 26.354495070191319, 57.522819932993457 ], [ 26.349844191156478, 57.52966705988689 ], [ 26.344263137034147, 57.533232734203295 ], [ 26.341162550711147, 57.537780260450631 ], [ 26.344108107403201, 57.542379461742712 ], [ 26.345658400115042, 57.551913763809125 ], [ 26.352169631123559, 57.560879625094572 ], [ 26.356975538889969, 57.57069814710178 ], [ 26.352634719116963, 57.576227525280046 ], [ 26.351325149735828, 57.580203000867755 ], [ 26.430678413000066, 57.561764252 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-101", "NAME_1": "Valkas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.714998413000103, 57.923390605000051 ], [ 25.730294637000071, 57.923132223 ], [ 25.745384155000067, 57.909179586000093 ], [ 25.754685913000088, 57.888896586000087 ], [ 25.767501668000079, 57.868923646000056 ], [ 25.792616415000083, 57.855978699000033 ], [ 26.002732788000088, 57.845979310000118 ], [ 26.014515015000086, 57.842827047000057 ], [ 26.021956421000112, 57.837943624000033 ], [ 26.025263713000101, 57.828409322000041 ], [ 26.021749715000055, 57.822414856000123 ], [ 26.016065308000094, 57.816472067000106 ], [ 26.013068074000074, 57.807299500000013 ], [ 26.016065308000094, 57.786680603000022 ], [ 26.024746948000143, 57.774355774000057 ], [ 26.135231161000092, 57.724772238000085 ], [ 26.168510783000016, 57.704049988000051 ], [ 26.26318200700004, 57.62082509400004 ], [ 26.297185099000075, 57.599146830000066 ], [ 26.33862959800004, 57.583152975000118 ], [ 26.351325149735828, 57.580203000867755 ], [ 26.352634719116963, 57.576227525280046 ], [ 26.356975538889969, 57.57069814710178 ], [ 26.352169631123559, 57.560879625094572 ], [ 26.345658400115042, 57.551913763809125 ], [ 26.344108107403201, 57.542379461742712 ], [ 26.341162550711147, 57.537780260450631 ], [ 26.344263137034147, 57.533232734203295 ], [ 26.349844191156478, 57.52966705988689 ], [ 26.354495070191319, 57.522819932993457 ], [ 26.362556593911677, 57.519848537879682 ], [ 26.362866652274192, 57.515636909315901 ], [ 26.361781446656437, 57.512820542933696 ], [ 26.311500278215249, 57.499332993822577 ], [ 26.301609569426091, 57.527702810136532 ], [ 26.282074370036128, 57.525260909763119 ], [ 26.24544587073035, 57.522819010289084 ], [ 26.240562070882845, 57.54235420967899 ], [ 26.184398373086424, 57.547238009526495 ], [ 26.155095574001507, 57.547238009526495 ], [ 26.123350875442554, 57.569215108390495 ], [ 26.094048076357637, 57.559447508695541 ], [ 26.050093877730262, 57.559447508695541 ], [ 25.97683688001797, 57.549679909000588 ], [ 25.945092181458961, 57.530144709610624 ], [ 25.896254182984137, 57.530144709610624 ], [ 25.888928482763276, 57.549679909000588 ], [ 25.903579882305678, 57.569215108390495 ], [ 25.901137982831642, 57.588750307780458 ], [ 25.876718983594174, 57.603401707322917 ], [ 25.932882681390595, 57.608285507170365 ], [ 25.959743581001419, 57.620495006339468 ], [ 25.979278780391382, 57.649797805424328 ], [ 25.989046380086336, 57.671774905187704 ], [ 25.859625684577679, 57.68398440435675 ], [ 25.857183784204267, 57.703519603746713 ], [ 25.82788098511935, 57.705961504120069 ], [ 25.847416184509314, 57.752357602221537 ], [ 25.735088788017151, 57.730380503357537 ], [ 25.688656200823004, 57.737670674062315 ], [ 25.660474006470224, 57.750196093374996 ], [ 25.616635037077288, 57.758024480445386 ], [ 25.596281229794897, 57.773681255485542 ], [ 25.564827507783264, 57.782649034125598 ], [ 25.552890252283305, 57.792390041766964 ], [ 25.555060662619439, 57.799573066343896 ], [ 25.560176628748422, 57.807582913220813 ], [ 25.555370720981955, 57.816057848091191 ], [ 25.542348259864298, 57.820657050282591 ], [ 25.526018507747892, 57.829958808352274 ], [ 25.511135694656559, 57.840991726287086 ], [ 25.466329999030222, 57.85979351685728 ], [ 25.469461354757698, 57.873884613584039 ], [ 25.501213819861903, 57.868225206028399 ], [ 25.522297803800598, 57.86887116207447 ], [ 25.535010206555796, 57.87440054025268 ], [ 25.546534050905734, 57.876881008951386 ], [ 25.5566109562306, 57.880033271218451 ], [ 25.565912713400962, 57.879929918430946 ], [ 25.574129265852946, 57.880834255996092 ], [ 25.582810907197597, 57.883133857541452 ], [ 25.613503682249132, 57.866056226513592 ], [ 25.65336801961837, 57.863651497818353 ], [ 25.657342650742692, 57.878581646725593 ], [ 25.660474006470224, 57.892672743452295 ], [ 25.666077060707778, 57.914373651522794 ], [ 25.703799772000053, 57.921326524000065 ], [ 25.714998413000103, 57.923390605000051 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-007", "NAME_1": "Aluksne" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.87265384900013, 57.627181295000085 ], [ 26.884952840000096, 57.622091166000089 ], [ 26.896114950000111, 57.614985657000048 ], [ 26.911617880000023, 57.61343536400004 ], [ 26.929187866000092, 57.615554098000118 ], [ 26.946551148000111, 57.615347392000061 ], [ 26.981071004000086, 57.608991191000129 ], [ 27.004635458000109, 57.598914286000095 ], [ 27.041325724000075, 57.56845103000002 ], [ 27.061531209000066, 57.555661113000056 ], [ 27.085354045000116, 57.549434103000053 ], [ 27.165659220000066, 57.546695252000106 ], [ 27.319965047000068, 57.516128642000083 ], [ 27.352934611000137, 57.527600810000123 ], [ 27.528169393000042, 57.528479309000048 ], [ 27.522795044000077, 57.492021587000053 ], [ 27.525637248000066, 57.468379619000032 ], [ 27.514836873000036, 57.447889913000054 ], [ 27.511322876000065, 57.430397441000096 ], [ 27.536282593000067, 57.415695496000112 ], [ 27.640824015000049, 57.389004618000072 ], [ 27.694408720000069, 57.356375287 ], [ 27.688522169962141, 57.332573147532685 ], [ 27.681855910222055, 57.326759549413623 ], [ 27.672347445678042, 57.320222479983443 ], [ 27.662270542151759, 57.318413804853208 ], [ 27.652348667357103, 57.313685411452525 ], [ 27.600207146942239, 57.297794908008598 ], [ 27.587339714556151, 57.282550361509948 ], [ 27.571216668014813, 57.276969306488297 ], [ 27.555558708567503, 57.258908392708122 ], [ 27.526826612957791, 57.259864407116652 ], [ 27.51892011886838, 57.259347643179183 ], [ 27.501711866709286, 57.264773668569944 ], [ 27.487087436935667, 57.273868720165297 ], [ 27.46073245633778, 57.274178779427075 ], [ 27.406265496855156, 57.263068346227158 ], [ 27.354899122796269, 57.284772446890884 ], [ 27.326011996656291, 57.277434394481702 ], [ 27.275885857846049, 57.246686917267368 ], [ 27.248135614167268, 57.248960680391065 ], [ 27.24100426643372, 57.246428534848974 ], [ 27.234182977062744, 57.242811183689128 ], [ 27.228911981302872, 57.238987127853648 ], [ 27.220488722376615, 57.23438792656151 ], [ 27.20111005988133, 57.234801336812211 ], [ 27.184366895715641, 57.228109239549724 ], [ 27.156564976092739, 57.226378078785274 ], [ 27.056157667941989, 57.24870229797267 ], [ 27.045925733885497, 57.250304267527895 ], [ 27.046545850610471, 57.253353176108135 ], [ 27.048561232215093, 57.256427924009472 ], [ 27.060085076565031, 57.263223374958784 ], [ 27.066906365936006, 57.268701077192929 ], [ 27.059930046934085, 57.27275767702514 ], [ 27.020810987636935, 57.286736152551384 ], [ 26.988513217710818, 57.304409491804563 ], [ 26.905520868193491, 57.303737698236148 ], [ 26.865006544916071, 57.299526068773048 ], [ 26.849348586368137, 57.315261541686766 ], [ 26.845472852789896, 57.315933336154501 ], [ 26.840046828298455, 57.3162950713604 ], [ 26.836171094720214, 57.315313219429527 ], [ 26.790230746951408, 57.318413804853208 ], [ 26.7607235040864, 57.321669419907778 ], [ 26.747701042968743, 57.318956407212397 ], [ 26.714783156317594, 57.306476549353249 ], [ 26.682227003973026, 57.320015774408432 ], [ 26.681451856717786, 57.323684801512343 ], [ 26.677576124938184, 57.334330145819536 ], [ 26.6331860689819, 57.350298162729985 ], [ 26.61814822805826, 57.362493801547657 ], [ 26.594222040214959, 57.391303412422531 ], [ 26.614617121882645, 57.419416600895374 ], [ 26.644236424771179, 57.417138192773336 ], [ 26.687526174593245, 57.417138192773336 ], [ 26.703475029648871, 57.423973416240131 ], [ 26.714867069359741, 57.464984758839535 ], [ 26.721702292826535, 57.490047245483936 ], [ 26.758156820081183, 57.503717693316844 ], [ 26.808281793370043, 57.503717693316844 ], [ 26.808281793370043, 57.51966654837247 ], [ 26.806003385248005, 57.55384266750508 ], [ 26.795759318000137, 57.571319071000104 ], [ 26.816326538000055, 57.581189270000081 ], [ 26.828315471000082, 57.595038555000045 ], [ 26.838547404000082, 57.609869690000082 ], [ 26.854153687000121, 57.622556254000116 ], [ 26.87265384900013, 57.627181295000085 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-108", "NAME_1": "Vilakas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.694408720000069, 57.356375287 ], [ 27.707951701000098, 57.348128561000053 ], [ 27.809134155000095, 57.313944601000074 ], [ 27.827737671000136, 57.304952902000124 ], [ 27.840295044000072, 57.290638529000049 ], [ 27.846134481000036, 57.267306621 ], [ 27.841173543000082, 57.211211853000052 ], [ 27.833938842000123, 57.180490215000091 ], [ 27.824327027000038, 57.159070333000088 ], [ 27.794044637000098, 57.143386536000023 ], [ 27.700975383000099, 57.118788555000052 ], [ 27.682216838000102, 57.103699036000037 ], [ 27.696066121000058, 57.085457256000083 ], [ 27.722059367000043, 57.077990011000068 ], [ 27.745468791000093, 57.06799062100005 ], [ 27.750739786000111, 57.042359111000067 ], [ 27.745778848914711, 57.031455382559841 ], [ 27.690025652961481, 57.022973625432428 ], [ 27.637622271550583, 57.018416809188352 ], [ 27.630787048083789, 57.038922480488054 ], [ 27.589775705484385, 57.04347929673213 ], [ 27.553321179129114, 57.038922480488054 ], [ 27.541929139418244, 57.07765541496542 ], [ 27.514588244651748, 57.079933823087458 ], [ 27.455349638874736, 57.114109941320692 ], [ 27.402946257463782, 57.159678099264852 ], [ 27.425730335986202, 57.212081481575069 ], [ 27.446236007285904, 57.243979191686321 ], [ 27.487087436935667, 57.273868720165297 ], [ 27.501711866709286, 57.264773668569944 ], [ 27.51892011886838, 57.259347643179183 ], [ 27.526826612957791, 57.259864407116652 ], [ 27.555558708567503, 57.258908392708122 ], [ 27.571216668014813, 57.276969306488297 ], [ 27.587339714556151, 57.282550361509948 ], [ 27.600207146942239, 57.297794908008598 ], [ 27.652348667357103, 57.313685411452525 ], [ 27.662270542151759, 57.318413804853208 ], [ 27.672347445678042, 57.320222479983443 ], [ 27.681855910222055, 57.326759549413623 ], [ 27.688522169962141, 57.332573147532685 ], [ 27.694408720000069, 57.356375287 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-014", "NAME_1": "Baltinavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.745778848914711, 57.031455382559841 ], [ 27.729552450000057, 57.009854635000053 ], [ 27.723351278000081, 56.998795878000053 ], [ 27.72076745600009, 56.988667298000095 ], [ 27.718545369000083, 56.968720195000074 ], [ 27.715134724000052, 56.957403056 ], [ 27.648730510000092, 56.879268291000059 ], [ 27.628369995000071, 56.844154155000027 ], [ 27.644379265690532, 56.841771673423978 ], [ 27.637155795903254, 56.836608588215768 ], [ 27.614986607246124, 56.845006008720304 ], [ 27.60082726366727, 56.852447414816311 ], [ 27.581293573339735, 56.857615057788678 ], [ 27.571836785639107, 56.863816230434679 ], [ 27.567650994597727, 56.867330227008324 ], [ 27.561604851582615, 56.87074087169384 ], [ 27.549615920138592, 56.866942654280024 ], [ 27.529927199280792, 56.862886054447813 ], [ 27.51814497161314, 56.857976792994577 ], [ 27.446701919957604, 56.852663522803311 ], [ 27.434843967575034, 56.883990742578703 ], [ 27.41661670439737, 56.902218005756311 ], [ 27.443957599163866, 56.915888453589218 ], [ 27.494082573352046, 56.91361004546718 ], [ 27.537372323174168, 56.934115716766883 ], [ 27.519145059996504, 56.954621388066585 ], [ 27.612559784006805, 56.984240690955119 ], [ 27.664963166317079, 56.988797506299875 ], [ 27.637622271550583, 57.018416809188352 ], [ 27.690025652961481, 57.022973625432428 ], [ 27.745778848914711, 57.031455382559841 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-044", "NAME_1": "Karsavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.644379265690532, 56.841771673423978 ], [ 27.661184530000099, 56.839270732 ], [ 27.744073527000097, 56.864798890000046 ], [ 27.7863965250001, 56.871258443000087 ], [ 27.830838257000096, 56.864282125000088 ], [ 27.852232300000111, 56.854308573000068 ], [ 27.891506388000067, 56.829710592000097 ], [ 27.913520548000065, 56.820150452000107 ], [ 27.918894897000115, 56.805887757000036 ], [ 27.900084676000091, 56.783046774000113 ], [ 27.876106811000085, 56.759430644000091 ], [ 27.86546146677324, 56.742687480242978 ], [ 27.843812261626567, 56.743357570495448 ], [ 27.820513392279224, 56.727824990930571 ], [ 27.820513392279224, 56.688993542917672 ], [ 27.802392049453488, 56.665694674469648 ], [ 27.760971838179785, 56.691582306178475 ], [ 27.644477494141029, 56.686404779656812 ], [ 27.592702229824056, 56.655339621426378 ], [ 27.58020836772198, 56.619438382275519 ], [ 27.551631300843837, 56.636749986322798 ], [ 27.541554396418235, 56.645069892461549 ], [ 27.524811232252546, 56.6517878281457 ], [ 27.512408887859863, 56.654836738524637 ], [ 27.501815220396111, 56.655766912712807 ], [ 27.488017612023157, 56.653312283335197 ], [ 27.476390414885714, 56.658454087885843 ], [ 27.472049595112708, 56.663259996551574 ], [ 27.470344272769978, 56.674215400120602 ], [ 27.480266147564635, 56.680829983017247 ], [ 27.486312289680427, 56.693387356141557 ], [ 27.471030359263636, 56.740768807234645 ], [ 27.45549777969876, 56.771833965465134 ], [ 27.403722515381787, 56.784777781769208 ], [ 27.339861280973309, 56.782684231092389 ], [ 27.347922803794347, 56.784312039069334 ], [ 27.344822219269986, 56.801597804694893 ], [ 27.348387891787752, 56.807204698138264 ], [ 27.348077834324556, 56.815137031548716 ], [ 27.355209181158784, 56.821648260758593 ], [ 27.362495557623845, 56.824593818350024 ], [ 27.368696730269846, 56.824464627140799 ], [ 27.371332227700123, 56.822009995964493 ], [ 27.376603224359314, 56.820614731984278 ], [ 27.38342451283097, 56.821364040817798 ], [ 27.394948358080228, 56.828030301457204 ], [ 27.390917595770418, 56.831906033236805 ], [ 27.395723504436148, 56.835962633069016 ], [ 27.412156610239379, 56.84531606708282 ], [ 27.427814568787312, 56.85125885731037 ], [ 27.446701919957604, 56.852663522803311 ], [ 27.51814497161314, 56.857976792994577 ], [ 27.529927199280792, 56.862886054447813 ], [ 27.549615920138592, 56.866942654280024 ], [ 27.561604851582615, 56.87074087169384 ], [ 27.567650994597727, 56.867330227008324 ], [ 27.571836785639107, 56.863816230434679 ], [ 27.581293573339735, 56.857615057788678 ], [ 27.60082726366727, 56.852447414816311 ], [ 27.614986607246124, 56.845006008720304 ], [ 27.637155795903254, 56.836608588215768 ], [ 27.644379265690532, 56.841771673423978 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-023", "NAME_1": "Ciblas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.86546146677324, 56.742687480242978 ], [ 27.882618042000047, 56.725479228000083 ], [ 27.981009969000098, 56.687006124000035 ], [ 27.991345255000084, 56.66997874 ], [ 27.992172079000113, 56.624994406000084 ], [ 27.997443074000046, 56.603832906000022 ], [ 28.010982299000119, 56.58755483 ], [ 28.028655640000125, 56.576754456000074 ], [ 28.108650757000078, 56.555179546000019 ], [ 28.126117391000037, 56.547763977000088 ], [ 28.132215210000084, 56.535981751000079 ], [ 28.125238892000084, 56.527145081000086 ], [ 28.099245647000117, 56.513399150000041 ], [ 28.093251180000095, 56.501591086000033 ], [ 28.053502081255999, 56.518135171301083 ], [ 27.988783000634953, 56.523312697822746 ], [ 27.94477402610039, 56.502602591736206 ], [ 27.918886394391563, 56.481892486548986 ], [ 27.88523247290027, 56.497425065214543 ], [ 27.864522366813731, 56.528490224344353 ], [ 27.812747102496758, 56.551789092792376 ], [ 27.776504417744661, 56.541434039749106 ], [ 27.755794311658121, 56.520723934561886 ], [ 27.724729153427688, 56.533667749966639 ], [ 27.691075231936395, 56.567321672357252 ], [ 27.657421310445102, 56.567321672357252 ], [ 27.603057282867269, 56.575087962139719 ], [ 27.56113976358921, 56.575926826361922 ], [ 27.561449822851046, 56.599646307730893 ], [ 27.58020836772198, 56.619438382275519 ], [ 27.592702229824056, 56.655339621426378 ], [ 27.644477494141029, 56.686404779656812 ], [ 27.760971838179785, 56.691582306178475 ], [ 27.802392049453488, 56.665694674469648 ], [ 27.820513392279224, 56.688993542917672 ], [ 27.820513392279224, 56.727824990930571 ], [ 27.843812261626567, 56.743357570495448 ], [ 27.86546146677324, 56.742687480242978 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-058", "NAME_1": "Ludzas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 28.093251180000095, 56.501591086000033 ], [ 28.096455119000069, 56.491953431000027 ], [ 28.104516642000135, 56.483065084000017 ], [ 28.156503133000086, 56.446219788000107 ], [ 28.16425459800007, 56.437951559000041 ], [ 28.167871947832111, 56.427125345957904 ], [ 28.118221160977782, 56.422350932449547 ], [ 28.069034660820932, 56.422350932449547 ], [ 28.022436923025566, 56.437883512014423 ], [ 27.978427948491003, 56.432705985492817 ], [ 27.991371763895756, 56.40422958962381 ], [ 28.012081869982296, 56.383519484436647 ], [ 27.991371763895756, 56.37575319465418 ], [ 27.95512907914366, 56.378341957914984 ], [ 27.913708867869957, 56.355043088567641 ], [ 27.877466183117861, 56.331744220119617 ], [ 27.841223498365764, 56.321389167076347 ], [ 27.864522366813731, 56.285146482324251 ], [ 27.91629763113076, 56.285146482324251 ], [ 27.931830210695637, 56.308445350772274 ], [ 28.00431558019983, 56.331744220119617 ], [ 28.043147028212786, 56.313622877293938 ], [ 28.094922292529759, 56.298090298628324 ], [ 28.120809924238586, 56.274791429280981 ], [ 28.089744766008153, 56.238548744528885 ], [ 28.070411003964637, 56.208507392502156 ], [ 28.0650883322607, 56.203288071787028 ], [ 28.06126427642522, 56.20354645420548 ], [ 28.056096632553533, 56.206957098890996 ], [ 28.052685987868017, 56.211065375566591 ], [ 28.048035108833176, 56.214398504987003 ], [ 28.039818556381249, 56.215690416179712 ], [ 28.031446974298433, 56.214476020252107 ], [ 28.020904981879369, 56.209644273164656 ], [ 28.00131961380913, 56.203288071787028 ], [ 27.913056267632612, 56.18370270371679 ], [ 27.898483513803114, 56.183237617522025 ], [ 27.88757978707747, 56.186648261308164 ], [ 27.852853224396767, 56.181945706329259 ], [ 27.838435500198159, 56.182307441535158 ], [ 27.822415806444269, 56.185666409377291 ], [ 27.810943638038452, 56.196260077740362 ], [ 27.778490837582069, 56.195639960116011 ], [ 27.743919305431575, 56.199774075213384 ], [ 27.722473586286981, 56.20664704052848 ], [ 27.701854689442371, 56.219488634492848 ], [ 27.687126905981927, 56.225095527036899 ], [ 27.667593214755016, 56.240236720748044 ], [ 27.683561231665465, 56.24726471569403 ], [ 27.692552931372688, 56.249822700557161 ], [ 27.700459426361419, 56.255016181051872 ], [ 27.698909132750259, 56.258065091430808 ], [ 27.694258253715418, 56.260752264805149 ], [ 27.686816846720092, 56.262870999197219 ], [ 27.686816846720092, 56.276306871464897 ], [ 27.715497267285059, 56.298476061021347 ], [ 27.711001417881107, 56.311136786933162 ], [ 27.698909132750259, 56.321988836815365 ], [ 27.690227492304928, 56.332375800502746 ], [ 27.687591993975332, 56.347258612694816 ], [ 27.689607374680577, 56.354700018790822 ], [ 27.689917433043092, 56.360616969697389 ], [ 27.670383741816238, 56.371804918162411 ], [ 27.640411410957825, 56.378781236265013 ], [ 27.62242801334213, 56.392475490951142 ], [ 27.623978306053971, 56.401079617030689 ], [ 27.620412631737565, 56.407151598467465 ], [ 27.62180789571778, 56.420690823522648 ], [ 27.607958612300081, 56.420484117048318 ], [ 27.60253258601, 56.418572089130578 ], [ 27.596486443894264, 56.418985501179918 ], [ 27.591060417604183, 56.421827704185148 ], [ 27.592145623221938, 56.424385688148959 ], [ 27.589665155422551, 56.428313096771944 ], [ 27.581603630802874, 56.433609930953537 ], [ 27.571681756008218, 56.442808336235714 ], [ 27.559692823664875, 56.457096870124417 ], [ 27.55938276530236, 56.464460760955319 ], [ 27.549925977601788, 56.478568426791412 ], [ 27.508688184811888, 56.460171617126377 ], [ 27.49638919410603, 56.477922472544037 ], [ 27.495459019018483, 56.488025214492041 ], [ 27.487087436935667, 56.499497381998538 ], [ 27.486002232217231, 56.508850816012341 ], [ 27.486777377673832, 56.514896959027396 ], [ 27.493753695776434, 56.520865586777404 ], [ 27.506827833737532, 56.52807444887668 ], [ 27.525121290615061, 56.529288844804341 ], [ 27.538453810095234, 56.53153677040558 ], [ 27.551166212850433, 56.535102443822723 ], [ 27.566565789879292, 56.541717026719368 ], [ 27.574317254337814, 56.556961575016658 ], [ 27.56113976358921, 56.575926826361922 ], [ 27.603057282867269, 56.575087962139719 ], [ 27.657421310445102, 56.567321672357252 ], [ 27.691075231936395, 56.567321672357252 ], [ 27.724729153427688, 56.533667749966639 ], [ 27.755794311658121, 56.520723934561886 ], [ 27.776504417744661, 56.541434039749106 ], [ 27.812747102496758, 56.551789092792376 ], [ 27.864522366813731, 56.528490224344353 ], [ 27.88523247290027, 56.497425065214543 ], [ 27.918886394391563, 56.481892486548986 ], [ 27.94477402610039, 56.502602591736206 ], [ 27.988783000634953, 56.523312697822746 ], [ 28.053502081255999, 56.518135171301083 ], [ 28.093251180000095, 56.501591086000033 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-110", "NAME_1": "Zilupes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 28.167871947832111, 56.427125345957904 ], [ 28.16425459800007, 56.392812195 ], [ 28.167148478000058, 56.369867859000053 ], [ 28.174434855000129, 56.349636536000062 ], [ 28.214794148000095, 56.281371969000091 ], [ 28.217274617000072, 56.270726624000119 ], [ 28.215310913000053, 56.255998841000022 ], [ 28.209419800000063, 56.24814402300008 ], [ 28.201358276000065, 56.24168446900002 ], [ 28.193141724000043, 56.231039124000048 ], [ 28.184408406000045, 56.207526347000041 ], [ 28.17862064600007, 56.183910218000037 ], [ 28.169112182000106, 56.161741028 ], [ 28.148906697000115, 56.142414043000045 ], [ 28.110976196000109, 56.156805929000072 ], [ 28.068239787000095, 56.147581686000066 ], [ 28.051100037936408, 56.140665997038127 ], [ 28.050928988681846, 56.14112132379006 ], [ 28.047259963376575, 56.150888169853147 ], [ 28.04369428906017, 56.158019518485958 ], [ 28.06260786266273, 56.182100735060828 ], [ 28.077542351698128, 56.196725164834447 ], [ 28.070411003964637, 56.208507392502156 ], [ 28.089744766008153, 56.238548744528885 ], [ 28.120809924238586, 56.274791429280981 ], [ 28.094922292529759, 56.298090298628324 ], [ 28.043147028212786, 56.313622877293938 ], [ 28.00431558019983, 56.331744220119617 ], [ 27.931830210695637, 56.308445350772274 ], [ 27.91629763113076, 56.285146482324251 ], [ 27.864522366813731, 56.285146482324251 ], [ 27.841223498365764, 56.321389167076347 ], [ 27.877466183117861, 56.331744220119617 ], [ 27.913708867869957, 56.355043088567641 ], [ 27.95512907914366, 56.378341957914984 ], [ 27.991371763895756, 56.37575319465418 ], [ 28.012081869982296, 56.383519484436647 ], [ 27.991371763895756, 56.40422958962381 ], [ 27.978427948491003, 56.432705985492817 ], [ 28.022436923025566, 56.437883512014423 ], [ 28.069034660820932, 56.422350932449547 ], [ 28.118221160977782, 56.422350932449547 ], [ 28.167871947832111, 56.427125345957904 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-065", "NAME_1": "Neretas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.349085703000128, 56.159876323000091 ], [ 25.10919559700011, 56.183083395 ], [ 25.073125447000109, 56.197811178 ], [ 25.043979940000071, 56.227990214 ], [ 25.026203247000041, 56.260184632000048 ], [ 25.016488077000105, 56.269899801000079 ], [ 24.983208456000057, 56.290492859 ], [ 24.973183228000039, 56.300750631000071 ], [ 24.962124471000038, 56.319276632000097 ], [ 24.936286255000113, 56.379918925000069 ], [ 24.910137980000115, 56.421957703000018 ], [ 24.892257934000071, 56.438726706000054 ], [ 24.8709672446426, 56.442602437352548 ], [ 24.877635972122391, 56.454549473879069 ], [ 24.900877881109295, 56.459912991891031 ], [ 24.911604916233898, 56.472427865753389 ], [ 24.920544112620689, 56.486730579252821 ], [ 25.013511748568305, 56.495669774740293 ], [ 25.027814462067738, 56.459912991891031 ], [ 25.051056371054642, 56.463488670265917 ], [ 25.074298280041546, 56.4027021378933 ], [ 25.133296972777032, 56.413429173017903 ], [ 25.29357791508869, 56.407900906401665 ], [ 25.298228794123531, 56.377075913922283 ], [ 25.298538853385367, 56.365552070471665 ], [ 25.302104525903133, 56.349377346187566 ], [ 25.298228794123531, 56.342504380872469 ], [ 25.296213413418286, 56.336949164272482 ], [ 25.299158970110341, 56.334158637211317 ], [ 25.305360141857022, 56.330670478160016 ], [ 25.361377394051487, 56.334752916413947 ], [ 25.370265740971149, 56.327311510317941 ], [ 25.362927686763328, 56.3226606312831 ], [ 25.341998731556203, 56.317027900317328 ], [ 25.324325392302967, 56.30932811090355 ], [ 25.31094119597941, 56.297752591508868 ], [ 25.310476108885325, 56.28736562782143 ], [ 25.33750288305157, 56.275893460314933 ], [ 25.356416456654131, 56.261630763948574 ], [ 25.37222944483301, 56.255791328307112 ], [ 25.403442010040692, 56.231555081202032 ], [ 25.395535515951281, 56.202099514281144 ], [ 25.408092889075533, 56.182462470266785 ], [ 25.408557977068995, 56.178586738487184 ], [ 25.403752069302527, 56.175486152164183 ], [ 25.37181603368299, 56.174142565027353 ], [ 25.359517042977131, 56.168199774799746 ], [ 25.349543491339034, 56.160241603866893 ], [ 25.349085703000128, 56.159876323000091 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-107", "NAME_1": "Viesites" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.454807577000111, 56.149648743000014 ], [ 25.349085703000128, 56.159876323000091 ], [ 25.349543491339034, 56.160241603866893 ], [ 25.359517042977131, 56.168199774799746 ], [ 25.37181603368299, 56.174142565027353 ], [ 25.403752069302527, 56.175486152164183 ], [ 25.408557977068995, 56.178586738487184 ], [ 25.408092889075533, 56.182462470266785 ], [ 25.395535515951281, 56.202099514281144 ], [ 25.403442010040692, 56.231555081202032 ], [ 25.37222944483301, 56.255791328307112 ], [ 25.356416456654131, 56.261630763948574 ], [ 25.33750288305157, 56.275893460314933 ], [ 25.310476108885325, 56.28736562782143 ], [ 25.31094119597941, 56.297752591508868 ], [ 25.324325392302967, 56.30932811090355 ], [ 25.341998731556203, 56.317027900317328 ], [ 25.362927686763328, 56.3226606312831 ], [ 25.370265740971149, 56.327311510317941 ], [ 25.361377394051487, 56.334752916413947 ], [ 25.305360141857022, 56.330670478160016 ], [ 25.299158970110341, 56.334158637211317 ], [ 25.296213413418286, 56.336949164272482 ], [ 25.298228794123531, 56.342504380872469 ], [ 25.302104525903133, 56.349377346187566 ], [ 25.298538853385367, 56.365552070471665 ], [ 25.298228794123531, 56.377075913922283 ], [ 25.29357791508869, 56.407900906401665 ], [ 25.296213413418286, 56.420820013832554 ], [ 25.331301711304889, 56.421285101825958 ], [ 25.393520135246035, 56.410975654303002 ], [ 25.455583529556236, 56.413352770214146 ], [ 25.476202427300166, 56.418882148392413 ], [ 25.505399610903282, 56.436400458014703 ], [ 25.523538039049242, 56.444823716940959 ], [ 25.547774286154379, 56.444875392885024 ], [ 25.566687859756883, 56.447614244002125 ], [ 25.573819206591111, 56.452859402239596 ], [ 25.583431023922572, 56.458569648470473 ], [ 25.590717401287009, 56.460843410694793 ], [ 25.594644809910051, 56.467147936128299 ], [ 25.615928578241267, 56.446736111782116 ], [ 25.611476223648765, 56.431152872507027 ], [ 25.635964172108856, 56.408891100443896 ], [ 25.658225943272669, 56.393307860269488 ], [ 25.676035360743299, 56.36659373541255 ], [ 25.727237434959648, 56.368819911809453 ], [ 25.733915966848372, 56.351010495238143 ], [ 25.736142143245274, 56.328748723175011 ], [ 25.733915966848372, 56.299808421021794 ], [ 25.680487715335801, 56.29312988913307 ], [ 25.693844778213929, 56.228570752038536 ], [ 25.693844778213929, 56.208535158171003 ], [ 25.67380918344702, 56.184047209710911 ], [ 25.649679402582365, 56.143809306554203 ], [ 25.590044800000044, 56.141122131000017 ], [ 25.572888224000081, 56.143034159000123 ], [ 25.522762085000068, 56.156573385000101 ], [ 25.454807577000111, 56.149648743000014 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-105", "NAME_1": "Vecumnieku" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.8709672446426, 56.442602437352548 ], [ 24.857117960000096, 56.43536773700005 ], [ 24.853397257000097, 56.424722393000067 ], [ 24.852157023000075, 56.413663635000049 ], [ 24.845852498000056, 56.404930319000087 ], [ 24.837687622000118, 56.403948466000017 ], [ 24.815570109000106, 56.408806051000042 ], [ 24.805544881000088, 56.408547669000072 ], [ 24.715039531000059, 56.386252988000038 ], [ 24.678627563000106, 56.37728342700008 ], [ 24.639146770000082, 56.359971822000077 ], [ 24.572772161366768, 56.309810859570966 ], [ 24.529007335520078, 56.322249375808042 ], [ 24.480735677909195, 56.309734501945684 ], [ 24.468220804046837, 56.331188572194833 ], [ 24.491462713033741, 56.343703446057134 ], [ 24.505765426533173, 56.361581837931453 ], [ 24.452130251809649, 56.366945355044095 ], [ 24.446766733797688, 56.381248068543528 ], [ 24.514704622020645, 56.397338620780715 ], [ 24.530795174257889, 56.409853494643016 ], [ 24.51291678328289, 56.454549473879069 ], [ 24.473584321159478, 56.483154900877935 ], [ 24.437827538310216, 56.474215704491144 ], [ 24.42710050318567, 56.493881936002538 ], [ 24.40028291582388, 56.509972488239725 ], [ 24.380616685211862, 56.529638718851743 ], [ 24.387768041961522, 56.552880627838704 ], [ 24.34843557983811, 56.565395501701005 ], [ 24.321617992476376, 56.56360766296325 ], [ 24.309103118614019, 56.611879320574133 ], [ 24.368101810450185, 56.619030677323849 ], [ 24.355358623901338, 56.661563541524174 ], [ 24.374047479407636, 56.659590969447663 ], [ 24.372962273789881, 56.666308906031134 ], [ 24.376062860112881, 56.66840180110222 ], [ 24.383659294940514, 56.670856432278526 ], [ 24.414975212935701, 56.674267076064666 ], [ 24.42789432126591, 56.677677720750182 ], [ 24.443397251082274, 56.684240627702764 ], [ 24.482361280748478, 56.704704494916484 ], [ 24.521893752094968, 56.713644517780267 ], [ 24.556051873095441, 56.706978258040124 ], [ 24.600907017045188, 56.707029933984245 ], [ 24.634083286114731, 56.676850898450198 ], [ 24.58974490700183, 56.661037910271318 ], [ 24.59284549332483, 56.643028673334584 ], [ 24.599666781796543, 56.620678615725524 ], [ 24.617185093217472, 56.607191067513725 ], [ 24.629897495073294, 56.601454982861128 ], [ 24.656769239608707, 56.599801337361782 ], [ 24.662505324261303, 56.596855781569047 ], [ 24.662970412254708, 56.593961899921737 ], [ 24.660334913925112, 56.5904995792921 ], [ 24.661420118643548, 56.588329168955966 ], [ 24.666070997678389, 56.587166449871745 ], [ 24.673667433405285, 56.586546332247394 ], [ 24.691185743926951, 56.581662910115199 ], [ 24.697128534154501, 56.578872382154657 ], [ 24.697231886942006, 56.576236883825118 ], [ 24.681418897863807, 56.571327623271145 ], [ 24.671341994337524, 56.566780097023809 ], [ 24.664365676234979, 56.556832383807432 ], [ 24.657079298870542, 56.543577378693044 ], [ 24.732475212660916, 56.544249173160779 ], [ 24.949987336346453, 56.578568175680743 ], [ 24.956300895469951, 56.551092789100892 ], [ 24.995633356694043, 56.551092789100892 ], [ 25.013511748568305, 56.495669774740293 ], [ 24.920544112620689, 56.486730579252821 ], [ 24.911604916233898, 56.472427865753389 ], [ 24.900877881109295, 56.459912991891031 ], [ 24.877635972122391, 56.454549473879069 ], [ 24.8709672446426, 56.442602437352548 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-088", "NAME_1": "Saldus" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.214901977000096, 56.39038340300003 ], [ 22.195264933000146, 56.394853414000053 ], [ 22.158161255000095, 56.410252991000036 ], [ 22.139144328000015, 56.415704854000111 ], [ 22.094082479000065, 56.417410177000065 ], [ 21.983173071000039, 56.388353479000031 ], [ 21.982668897951271, 56.388780626324831 ], [ 21.980808546876915, 56.390356757458335 ], [ 21.945565220258686, 56.416711738056222 ], [ 21.951352979956027, 56.421130073094389 ], [ 21.961584914012576, 56.426271878544355 ], [ 21.976932814197994, 56.429114081549585 ], [ 21.987784864979517, 56.432524726235101 ], [ 21.99538130070647, 56.439320177184413 ], [ 21.989490187322247, 56.446115628133725 ], [ 21.954608595010654, 56.4706360951796 ], [ 21.957554151702709, 56.480842189915109 ], [ 21.953988478285623, 56.48781850891703 ], [ 21.956468946984273, 56.493606269513691 ], [ 21.966700881040822, 56.506628729732086 ], [ 21.99786176940512, 56.50593109774195 ], [ 22.009695672117573, 56.507972316868916 ], [ 22.024268425947071, 56.50745555293139 ], [ 22.037445915796354, 56.504044908245874 ], [ 22.056049431935719, 56.509057522486671 ], [ 22.05744469591599, 56.523268541110269 ], [ 22.049073113833117, 56.529056300807611 ], [ 22.018222283831335, 56.542595526762113 ], [ 22.011711052822818, 56.547504788215349 ], [ 22.011400995359622, 56.555049547098918 ], [ 22.060596958183055, 56.564506333900169 ], [ 22.099974399898599, 56.56853709620998 ], [ 22.121265090311624, 56.576340237511943 ], [ 22.126070998078092, 56.582205512474388 ], [ 22.123435499748496, 56.58763153696583 ], [ 22.118577915138644, 56.592359931265776 ], [ 22.115373976028138, 56.603806261249929 ], [ 22.112531773022909, 56.609516507480805 ], [ 22.109276157068962, 56.613624783257137 ], [ 22.111911655398558, 56.625691229966264 ], [ 22.111446567405153, 56.646671861117454 ], [ 22.103540073315742, 56.672871812983772 ], [ 22.112221713761073, 56.682948717409374 ], [ 22.125450881353061, 56.69013174108693 ], [ 22.155991652093064, 56.700311998300037 ], [ 22.166998731606157, 56.708218492389506 ], [ 22.173975050608078, 56.715969956848028 ], [ 22.181003044654744, 56.72852732997228 ], [ 22.188909538744213, 56.735658677705771 ], [ 22.189219598006048, 56.741756497564268 ], [ 22.185963982951421, 56.748112698042576 ], [ 22.174491815444924, 56.765889390982579 ], [ 22.162657911833151, 56.795034897742312 ], [ 22.204877556553981, 56.795215765794921 ], [ 22.230302362064322, 56.80906505011194 ], [ 22.251127963584622, 56.823095201582248 ], [ 22.269731479723987, 56.82970978537827 ], [ 22.305749952698136, 56.838623968921013 ], [ 22.316602004378979, 56.843662421583474 ], [ 22.34130333767888, 56.847486477418954 ], [ 22.342026808090679, 56.864229640685323 ], [ 22.329469434966427, 56.87477163400365 ], [ 22.32032270742701, 56.880404364969422 ], [ 22.310555861363866, 56.883763332811554 ], [ 22.30388960072446, 56.884512640745754 ], [ 22.295518018641644, 56.883298244818093 ], [ 22.287869906970627, 56.883091539243139 ], [ 22.269731479723987, 56.888052477539759 ], [ 22.26399539597071, 56.893943590024605 ], [ 22.267406039756906, 56.898542792216062 ], [ 22.287869906970627, 56.905777492737059 ], [ 22.290247022881772, 56.91006663746532 ], [ 22.304354688717865, 56.919730129841582 ], [ 22.328384230247991, 56.916836249093592 ], [ 22.335670606713109, 56.913012193258112 ], [ 22.338512810617715, 56.908025418338354 ], [ 22.338151076311078, 56.904072171293649 ], [ 22.348744744674207, 56.896424058723312 ], [ 22.392307977431187, 56.885882066304305 ], [ 22.417784457986329, 56.889344386933885 ], [ 22.432357211815884, 56.898542792216062 ], [ 22.443364292228296, 56.903555406456803 ], [ 22.454888135678914, 56.917094632411306 ], [ 22.492405226320102, 56.920091865047482 ], [ 22.511938918446333, 56.917404689874502 ], [ 22.526563348219952, 56.912960517313991 ], [ 22.529198845650171, 56.908774726272554 ], [ 22.524392937883761, 56.905105699168644 ], [ 22.520982293198244, 56.900299791402233 ], [ 22.52113732282919, 56.88464183195498 ], [ 22.518966913392319, 56.876838691552337 ], [ 22.514626091820674, 56.871981106043165 ], [ 22.506564568999636, 56.86748525753859 ], [ 22.500466750040459, 56.862989407235318 ], [ 22.490854932708942, 56.851517238829501 ], [ 22.462810392185816, 56.805420994483256 ], [ 22.442234072844144, 56.771127128913861 ], [ 22.460524134780997, 56.736833262445089 ], [ 22.487959227236558, 56.723115716217308 ], [ 22.497104258654588, 56.684249334938841 ], [ 22.501676773464283, 56.638524180546483 ], [ 22.535970639933055, 56.620234119509007 ], [ 22.549688186160836, 56.579081479926344 ], [ 22.574837020312202, 56.567650192002759 ], [ 22.60455837107196, 56.599657799268016 ], [ 22.654856041173332, 56.604230314977031 ], [ 22.693722422451799, 56.588226511344374 ], [ 22.712074313678613, 56.565372530317973 ], [ 22.701436395073529, 56.493296210251856 ], [ 22.741330600726599, 56.475442002946068 ], [ 22.744896275043061, 56.46812978805923 ], [ 22.748151890097631, 56.455701606144203 ], [ 22.742880894337759, 56.44926789040079 ], [ 22.736628044848374, 56.443790188166645 ], [ 22.726551141322091, 56.424540716880529 ], [ 22.767530551693596, 56.421827704185148 ], [ 22.785979038202015, 56.418417060399008 ], [ 22.821842482444595, 56.402707425007634 ], [ 22.858532748987159, 56.397229722773432 ], [ 22.859602845994516, 56.397069961809279 ], [ 22.822926880000097, 56.382864482000073 ], [ 22.681436808000058, 56.350308330000033 ], [ 22.666347290000147, 56.34909393400001 ], [ 22.649087361000085, 56.353098857000091 ], [ 22.604748982000018, 56.379092102000058 ], [ 22.578221703000111, 56.3870029160001 ], [ 22.575810180000076, 56.387722067000098 ], [ 22.511731404000045, 56.395964458000051 ], [ 22.214901977000096, 56.39038340300003 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-081", "NAME_1": "Rucavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.485493169569811, 56.265791916040953 ], [ 21.419291626000131, 56.237395325000065 ], [ 21.403685343000092, 56.234475607000078 ], [ 21.35831343600006, 56.233235373000028 ], [ 21.327617635000109, 56.224372864000017 ], [ 21.290410603000112, 56.206932068000086 ], [ 21.254857218000041, 56.185202128000086 ], [ 21.229639119000097, 56.163187968 ], [ 21.212585897000054, 56.13099355100006 ], [ 21.205351196000095, 56.103424174000068 ], [ 21.190365031000113, 56.084458924 ], [ 21.150160767000102, 56.078180237000097 ], [ 21.091352986000118, 56.077896017000015 ], [ 21.053396030000044, 56.072617906000062 ], [ 21.053396030000044, 56.072943427000041 ], [ 21.052582227000073, 56.077541408000059 ], [ 21.04273522200009, 56.114569403000075 ], [ 21.028819207000083, 56.147406317000048 ], [ 20.98406009200005, 56.210435289000031 ], [ 20.973399285000085, 56.232001044000071 ], [ 20.969086134000065, 56.253485419000071 ], [ 20.971039259000065, 56.259670315000051 ], [ 21.034466751639286, 56.266395796628672 ], [ 21.055786793225934, 56.168323604250929 ], [ 21.083502847378497, 56.166191600362026 ], [ 21.083502847378497, 56.191775649726424 ], [ 21.085634852166663, 56.228019721233125 ], [ 21.109086897642214, 56.219491704778306 ], [ 21.16451900684666, 56.264263792739825 ], [ 21.179443035867394, 56.28771583821532 ], [ 21.183707044544462, 56.323959909722021 ], [ 21.179443035867394, 56.355939972551653 ], [ 21.226347127717759, 56.351675963874584 ], [ 21.26259119922446, 56.326091913610924 ], [ 21.379851429300004, 56.32822391839909 ], [ 21.452339571414086, 56.302639868135373 ], [ 21.485493169569811, 56.265791916040953 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-074", "NAME_1": "Priekules" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.736514296197203, 56.323732366476086 ], [ 21.684495076000076, 56.310104066000022 ], [ 21.596025025000131, 56.307881979000015 ], [ 21.558921346000147, 56.297288310000042 ], [ 21.485493169569811, 56.265791916040953 ], [ 21.452339571414086, 56.302639868135373 ], [ 21.379851429300004, 56.32822391839909 ], [ 21.416095500806705, 56.353807968662807 ], [ 21.437415542393353, 56.407108073079087 ], [ 21.386247441865919, 56.464672186172436 ], [ 21.384115437977073, 56.490256236436153 ], [ 21.354267379036287, 56.503048261567983 ], [ 21.407567483452567, 56.520104294477562 ], [ 21.399039466997806, 56.558480369873166 ], [ 21.433151533716284, 56.545688344741279 ], [ 21.473659613900054, 56.520104294477562 ], [ 21.482187630354815, 56.511576278022801 ], [ 21.52909172220518, 56.513708281911647 ], [ 21.499243664163771, 56.552084357307194 ], [ 21.52909172220518, 56.571272395004996 ], [ 21.571731806277796, 56.560612373762012 ], [ 21.584523831409683, 56.549952353418348 ], [ 21.661275982200777, 56.560612373762012 ], [ 21.712444082728211, 56.549952353418348 ], [ 21.708180074051143, 56.505180265456886 ], [ 21.73376412431486, 56.498784252890914 ], [ 21.73376412431486, 56.473200202627197 ], [ 21.680464019898579, 56.464672186172436 ], [ 21.684728027676329, 56.432692123342804 ], [ 21.665539989978527, 56.417768093422751 ], [ 21.701784061485228, 56.396448051836103 ], [ 21.723104103071876, 56.38152402281537 ], [ 21.750820158123759, 56.38152402281537 ], [ 21.750820158123759, 56.36873199768354 ], [ 21.725236107860098, 56.355939972551653 ], [ 21.736514296197203, 56.323732366476086 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-100", "NAME_1": "Vainodes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.965321874000068, 56.383676718000046 ], [ 21.736514296197203, 56.323732366476086 ], [ 21.725236107860098, 56.355939972551653 ], [ 21.750820158123759, 56.36873199768354 ], [ 21.750820158123759, 56.38152402281537 ], [ 21.723104103071876, 56.38152402281537 ], [ 21.701784061485228, 56.396448051836103 ], [ 21.665539989978527, 56.417768093422751 ], [ 21.684728027676329, 56.432692123342804 ], [ 21.680464019898579, 56.464672186172436 ], [ 21.73376412431486, 56.473200202627197 ], [ 21.73376412431486, 56.498784252890914 ], [ 21.708180074051143, 56.505180265456886 ], [ 21.712444082728211, 56.549952353418348 ], [ 21.752952162012662, 56.562744378550178 ], [ 21.795393507604274, 56.573343003976447 ], [ 21.793223097268083, 56.554377753530446 ], [ 21.807485792735122, 56.538461412564118 ], [ 21.818027785154129, 56.530839138415502 ], [ 21.833065626977088, 56.523966173100405 ], [ 21.850790643073708, 56.521149806718199 ], [ 21.866138543259126, 56.520426337205663 ], [ 21.893475375787943, 56.523010158691818 ], [ 21.904172397837897, 56.521511541924099 ], [ 21.908358188879276, 56.517454942091888 ], [ 21.910063511222063, 56.512054755122847 ], [ 21.912388950289824, 56.507920640924794 ], [ 21.920605502741751, 56.506938788094544 ], [ 21.956934034977735, 56.514173489514917 ], [ 21.966700881040822, 56.506628729732086 ], [ 21.956468946984273, 56.493606269513691 ], [ 21.953988478285623, 56.48781850891703 ], [ 21.957554151702709, 56.480842189915109 ], [ 21.954608595010654, 56.4706360951796 ], [ 21.989490187322247, 56.446115628133725 ], [ 21.99538130070647, 56.439320177184413 ], [ 21.987784864979517, 56.432524726235101 ], [ 21.976932814197994, 56.429114081549585 ], [ 21.961584914012576, 56.426271878544355 ], [ 21.951352979956027, 56.421130073094389 ], [ 21.945565220258686, 56.416711738056222 ], [ 21.980808546876915, 56.390356757458335 ], [ 21.982668897951271, 56.388780626324831 ], [ 21.983173071000039, 56.388353479000031 ], [ 21.965321874000068, 56.383676718000046 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-010", "NAME_1": "Auces" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.062292114000059, 56.304161276000102 ], [ 23.01671350100014, 56.32384999600005 ], [ 22.981366822000069, 56.373175151000041 ], [ 22.95697554500012, 56.401984762000083 ], [ 22.924522746, 56.41211334300003 ], [ 22.888969360000146, 56.408444316000029 ], [ 22.859602845994516, 56.397069961809279 ], [ 22.858532748987159, 56.397229722773432 ], [ 22.821842482444595, 56.402707425007634 ], [ 22.785979038202015, 56.418417060399008 ], [ 22.767530551693596, 56.421827704185148 ], [ 22.726551141322091, 56.424540716880529 ], [ 22.736628044848374, 56.443790188166645 ], [ 22.742880894337759, 56.44926789040079 ], [ 22.748151890097631, 56.455701606144203 ], [ 22.744896275043061, 56.46812978805923 ], [ 22.741330600726599, 56.475442002946068 ], [ 22.701436395073529, 56.493296210251856 ], [ 22.712074313678613, 56.565372530317973 ], [ 22.732287225075368, 56.565281481155409 ], [ 22.735904575335894, 56.565539863573804 ], [ 22.755128208200233, 56.562025865201463 ], [ 22.773421665077763, 56.567503567435665 ], [ 22.798123000176304, 56.56833038973565 ], [ 22.82340877409564, 56.561819824225495 ], [ 22.834135809220186, 56.540365753976346 ], [ 22.848438522719619, 56.538577914339271 ], [ 22.862741235319731, 56.552880627838704 ], [ 22.896710179431238, 56.56718334133808 ], [ 22.930679123542689, 56.568971180075891 ], [ 22.925315606430104, 56.588637411587229 ], [ 22.961072389279309, 56.601152285449587 ], [ 22.980738620790703, 56.577910376462683 ], [ 23.021858921651869, 56.59042525032504 ], [ 23.041525152263944, 56.577910376462683 ], [ 23.066554899988603, 56.551092789100892 ], [ 23.088008970237752, 56.545729271088987 ], [ 23.086221131499997, 56.524275201739158 ], [ 23.089796809874883, 56.479579222503105 ], [ 23.121977914349259, 56.470640026116257 ], [ 23.129129271098975, 56.445610278391598 ], [ 23.148795501710993, 56.43309540362992 ], [ 23.118402235974372, 56.409853494643016 ], [ 23.152371180085879, 56.390187264030999 ], [ 23.172569619592252, 56.357956441436613 ], [ 23.154172811000109, 56.351393534000024 ], [ 23.162854451000072, 56.341006572000097 ], [ 23.112728312000058, 56.310879212000017 ], [ 23.062292114000059, 56.304161276000102 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-016", "NAME_1": "Bauska" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.572772161366768, 56.309810859570966 ], [ 24.557911417000099, 56.29858022100008 ], [ 24.538997843000061, 56.287624817 ], [ 24.481326945000092, 56.268840435000087 ], [ 24.447220499000139, 56.260520528000043 ], [ 24.414354289000102, 56.265507304 ], [ 24.350792277000039, 56.291552226000064 ], [ 24.31854618400007, 56.29858022100008 ], [ 24.287643677000091, 56.295531311000062 ], [ 24.166824178000041, 56.260313823000089 ], [ 24.140421618000062, 56.257600226000093 ], [ 24.138918905000082, 56.257445780000111 ], [ 24.108636516000047, 56.260701396000101 ], [ 24.075150187000133, 56.271191712000032 ], [ 23.981305786000036, 56.312403666000037 ], [ 24.008746138723325, 56.322249375808042 ], [ 24.035563726984378, 56.331188572194833 ], [ 24.04092724409702, 56.35085480280685 ], [ 24.073108348571395, 56.363369676669208 ], [ 24.06774483145881, 56.383035907281283 ], [ 24.090986740445715, 56.383035907281283 ], [ 24.126743524194296, 56.36873319378185 ], [ 24.155348950293785, 56.386611585656112 ], [ 24.114228649432619, 56.400914299155545 ], [ 24.064169153083924, 56.415217012654978 ], [ 24.03735156572219, 56.425944047779581 ], [ 24.035563726984378, 56.447398117129353 ], [ 24.030200208972474, 56.477791382865973 ], [ 24.017743371059737, 56.502013034873642 ], [ 24.031742790897056, 56.501719469178113 ], [ 24.042904900940414, 56.502830512318269 ], [ 24.080938754619808, 56.527454332151706 ], [ 24.078871697970442, 56.535464179028622 ], [ 24.105289453945147, 56.529638718851743 ], [ 24.1499854331812, 56.524275201739158 ], [ 24.173227342168104, 56.517123844989442 ], [ 24.194681412417253, 56.509972488239725 ], [ 24.234013873641345, 56.501033291852934 ], [ 24.251892265515608, 56.518911683727197 ], [ 24.300163923126547, 56.535002235964384 ], [ 24.34485990146328, 56.536790075601459 ], [ 24.380616685211862, 56.529638718851743 ], [ 24.40028291582388, 56.509972488239725 ], [ 24.42710050318567, 56.493881936002538 ], [ 24.437827538310216, 56.474215704491144 ], [ 24.473584321159478, 56.483154900877935 ], [ 24.51291678328289, 56.454549473879069 ], [ 24.530795174257889, 56.409853494643016 ], [ 24.514704622020645, 56.397338620780715 ], [ 24.446766733797688, 56.381248068543528 ], [ 24.452130251809649, 56.366945355044095 ], [ 24.505765426533173, 56.361581837931453 ], [ 24.491462713033741, 56.343703446057134 ], [ 24.468220804046837, 56.331188572194833 ], [ 24.480735677909195, 56.309734501945684 ], [ 24.529007335520078, 56.322249375808042 ], [ 24.572772161366768, 56.309810859570966 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-098", "NAME_1": "Tervetes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.37803970300007, 56.354629849000034 ], [ 23.31064904800013, 56.37059132900005 ], [ 23.288428182000075, 56.373045960000113 ], [ 23.172569619592252, 56.357956441436613 ], [ 23.152371180085879, 56.390187264030999 ], [ 23.118402235974372, 56.409853494643016 ], [ 23.148795501710993, 56.43309540362992 ], [ 23.172037410697953, 56.45097379550424 ], [ 23.214945551196195, 56.454549473879069 ], [ 23.275732082669435, 56.454549473879069 ], [ 23.309701026780942, 56.459912991891031 ], [ 23.288246956531793, 56.468852187378502 ], [ 23.273944243032361, 56.490306257627651 ], [ 23.315064543893584, 56.513548166614555 ], [ 23.35618484475475, 56.545729271088987 ], [ 23.383002432116541, 56.545729271088987 ], [ 23.393729467241087, 56.518911683727197 ], [ 23.415087925167597, 56.493296210251856 ], [ 23.430745883715531, 56.477586574860538 ], [ 23.427800327023476, 56.472884019881576 ], [ 23.42593997594912, 56.46893077283687 ], [ 23.415397984429433, 56.461463528319143 ], [ 23.413072543563032, 56.459448146714578 ], [ 23.418808628215629, 56.4494487584534 ], [ 23.430900913346477, 56.445185452146859 ], [ 23.439427525060239, 56.436658841332417 ], [ 23.449504428586465, 56.421879381028589 ], [ 23.462061801710774, 56.407151598467465 ], [ 23.46237186007329, 56.397953193185288 ], [ 23.456945834682529, 56.391648667751781 ], [ 23.449969516579927, 56.38627431920446 ], [ 23.442373080852974, 56.382062689741304 ], [ 23.430435825353015, 56.377902737121588 ], [ 23.420203892195843, 56.375887356416342 ], [ 23.384805535946725, 56.360746161805878 ], [ 23.378345981781592, 56.354906725265153 ], [ 23.37803970300007, 56.354629849000034 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-JEL", "NAME_1": "Jelgava" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.517561482000133, 56.328655904000058 ], [ 23.481594686000079, 56.330102845000042 ], [ 23.37803970300007, 56.354629849000034 ], [ 23.378345981781592, 56.354906725265153 ], [ 23.384805535946725, 56.360746161805878 ], [ 23.420203892195843, 56.375887356416342 ], [ 23.430435825353015, 56.377902737121588 ], [ 23.442373080852974, 56.382062689741304 ], [ 23.449969516579927, 56.38627431920446 ], [ 23.456945834682529, 56.391648667751781 ], [ 23.46237186007329, 56.397953193185288 ], [ 23.462061801710774, 56.407151598467465 ], [ 23.449504428586465, 56.421879381028589 ], [ 23.439427525060239, 56.436658841332417 ], [ 23.430900913346477, 56.445185452146859 ], [ 23.418808628215629, 56.4494487584534 ], [ 23.413072543563032, 56.459448146714578 ], [ 23.415397984429433, 56.461463528319143 ], [ 23.42593997594912, 56.46893077283687 ], [ 23.427800327023476, 56.472884019881576 ], [ 23.430745883715531, 56.477586574860538 ], [ 23.415087925167597, 56.493296210251856 ], [ 23.437877232348399, 56.499264838001864 ], [ 23.44748904788122, 56.505621039379434 ], [ 23.459271274649609, 56.529831448062851 ], [ 23.458651157025258, 56.544817613042369 ], [ 23.445938755169379, 56.556393134235748 ], [ 23.434466586763563, 56.564092922750149 ], [ 23.432141147695802, 56.57083669685602 ], [ 23.436792025831323, 56.577063707024422 ], [ 23.448419223868086, 56.579079087729667 ], [ 23.477151320377118, 56.586933905875014 ], [ 23.480251905800799, 56.599542954943388 ], [ 23.405941195829485, 56.606441759579525 ], [ 23.412917514831406, 56.616027736690683 ], [ 23.424699740700476, 56.622125556549179 ], [ 23.438187289811538, 56.634450384777381 ], [ 23.444543491189165, 56.641555894988528 ], [ 23.476066114759362, 56.656180324762147 ], [ 23.485367872829045, 56.660288601437799 ], [ 23.484282668110609, 56.664913642050919 ], [ 23.479786817807394, 56.676773383185036 ], [ 23.481492140150124, 56.680416571867283 ], [ 23.478236525095554, 56.684809068483673 ], [ 23.462991977697584, 56.692534695419852 ], [ 23.442683140114809, 56.696798000827073 ], [ 23.417413364235358, 56.706823229308554 ], [ 23.436481968368128, 56.723876450937439 ], [ 23.459271274649609, 56.734573472987336 ], [ 23.502886183350654, 56.746872463693194 ], [ 23.525262079381434, 56.763667303803004 ], [ 23.50552168078093, 56.808212389390235 ], [ 23.48660810717837, 56.815912177005316 ], [ 23.461958448923269, 56.884228420804959 ], [ 23.506761916029575, 56.894615384492397 ], [ 23.587635532054151, 56.891204738907561 ], [ 23.600813022802754, 56.889292710989764 ], [ 23.626237827413775, 56.8892410350457 ], [ 23.669542677752361, 56.874900825212876 ], [ 23.70726647306924, 56.852137356453795 ], [ 23.718428582213278, 56.835962633069016 ], [ 23.734086540761211, 56.822009995964493 ], [ 23.769639926641275, 56.800047511983053 ], [ 23.759718051846619, 56.771883857154251 ], [ 23.764213901250514, 56.768266506893724 ], [ 23.756307407161103, 56.744133613475469 ], [ 23.701237802982178, 56.737028061895501 ], [ 23.701237802982178, 56.720937509658313 ], [ 23.670844537245557, 56.715573991646352 ], [ 23.683359411107915, 56.697695599772089 ], [ 23.710176999369025, 56.70663479615888 ], [ 23.731209870339228, 56.690498589838 ], [ 23.683804230181067, 56.674098589877985 ], [ 23.644412690693116, 56.677963454250005 ], [ 23.617950646704969, 56.669543712572249 ], [ 23.631181668699014, 56.650298589508282 ], [ 23.676992949365115, 56.629895770073688 ], [ 23.670992949664878, 56.601501410361777 ], [ 23.707587308907137, 56.591887308752291 ], [ 23.739007050035298, 56.6076591070821 ], [ 23.778804230530795, 56.6175788486849 ], [ 23.769818331450836, 56.636687308972114 ], [ 23.791941897500578, 56.646880719669468 ], [ 23.82817438394062, 56.627969872811377 ], [ 23.865719006427014, 56.615454998949019 ], [ 23.912202824400822, 56.608303642199303 ], [ 23.940808251399687, 56.579698215200438 ], [ 23.964050160386591, 56.554668467475778 ], [ 23.969413677499176, 56.518911683727197 ], [ 23.99231367413671, 56.502546292377474 ], [ 23.962651400741549, 56.497843736499192 ], [ 23.95009402761724, 56.488025214492041 ], [ 23.932110630001546, 56.482754217832849 ], [ 23.920018344870698, 56.472728990250687 ], [ 23.886015251702474, 56.466114407353984 ], [ 23.875008172189382, 56.462858792299414 ], [ 23.861675652709209, 56.44849274404487 ], [ 23.847516310029675, 56.438829250769288 ], [ 23.840695020658643, 56.434178371734447 ], [ 23.841005079920478, 56.43035431589891 ], [ 23.842710402263208, 56.427408759206855 ], [ 23.850151808359271, 56.423377996896988 ], [ 23.85046186672173, 56.418468736343073 ], [ 23.84798139802308, 56.411750799759545 ], [ 23.843020460625723, 56.401828924964889 ], [ 23.838679639953398, 56.397023017198478 ], [ 23.836664260147472, 56.393250637307062 ], [ 23.836199172154068, 56.387850450337965 ], [ 23.873922966571627, 56.389478258314966 ], [ 23.879348992861708, 56.384284776021559 ], [ 23.873612909108431, 56.373768622024272 ], [ 23.870822381147946, 56.36715403912757 ], [ 23.868651970811811, 56.34327952722839 ], [ 23.871029086722956, 56.332427477346187 ], [ 23.871106924844696, 56.332072130024756 ], [ 23.85810917200007, 56.334391989000025 ], [ 23.825036255000072, 56.33490875300005 ], [ 23.756099894000045, 56.325968730000042 ], [ 23.724990682000112, 56.328810934 ], [ 23.723647094000029, 56.332118226 ], [ 23.72478397600014, 56.338396912000107 ], [ 23.723543742000118, 56.345812480000021 ], [ 23.715068807000108, 56.352737122000107 ], [ 23.706697225000113, 56.354442444000071 ], [ 23.679412069000108, 56.351858622000051 ], [ 23.609855591000013, 56.353822327 ], [ 23.577712850000097, 56.348603007000079 ], [ 23.517561482000133, 56.328655904000058 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-083", "NAME_1": "Rundales" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.981305786000036, 56.312403666000037 ], [ 23.871106924844696, 56.332072130024756 ], [ 23.871029086722956, 56.332427477346187 ], [ 23.868651970811811, 56.34327952722839 ], [ 23.870822381147946, 56.36715403912757 ], [ 23.873612909108431, 56.373768622024272 ], [ 23.879348992861708, 56.384284776021559 ], [ 23.873922966571627, 56.389478258314966 ], [ 23.836199172154068, 56.387850450337965 ], [ 23.836664260147472, 56.393250637307062 ], [ 23.838679639953398, 56.397023017198478 ], [ 23.843020460625723, 56.401828924964889 ], [ 23.84798139802308, 56.411750799759545 ], [ 23.85046186672173, 56.418468736343073 ], [ 23.850151808359271, 56.423377996896988 ], [ 23.842710402263208, 56.427408759206855 ], [ 23.841005079920478, 56.43035431589891 ], [ 23.840695020658643, 56.434178371734447 ], [ 23.847516310029675, 56.438829250769288 ], [ 23.861675652709209, 56.44849274404487 ], [ 23.875008172189382, 56.462858792299414 ], [ 23.886015251702474, 56.466114407353984 ], [ 23.920018344870698, 56.472728990250687 ], [ 23.932110630001546, 56.482754217832849 ], [ 23.95009402761724, 56.488025214492041 ], [ 23.962651400741549, 56.497843736499192 ], [ 23.99231367413671, 56.502546292377474 ], [ 24.017743371059737, 56.502013034873642 ], [ 24.030200208972474, 56.477791382865973 ], [ 24.035563726984378, 56.447398117129353 ], [ 24.03735156572219, 56.425944047779581 ], [ 24.064169153083924, 56.415217012654978 ], [ 24.114228649432619, 56.400914299155545 ], [ 24.155348950293785, 56.386611585656112 ], [ 24.126743524194296, 56.36873319378185 ], [ 24.090986740445715, 56.383035907281283 ], [ 24.06774483145881, 56.383035907281283 ], [ 24.073108348571395, 56.363369676669208 ], [ 24.04092724409702, 56.35085480280685 ], [ 24.035563726984378, 56.331188572194833 ], [ 24.008746138723325, 56.322249375808042 ], [ 23.981305786000036, 56.312403666000037 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-004", "NAME_1": "Aknistes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.795496034000053, 56.040130340000061 ], [ 25.766261434000114, 56.058646546 ], [ 25.708797241000099, 56.081048279000086 ], [ 25.69660160300009, 56.087895406000044 ], [ 25.687816610000112, 56.098334046000062 ], [ 25.669213094000071, 56.132027080000043 ], [ 25.661875041000059, 56.140812073000021 ], [ 25.649679402582365, 56.143809306554203 ], [ 25.67380918344702, 56.184047209710911 ], [ 25.693844778213929, 56.208535158171003 ], [ 25.693844778213929, 56.228570752038536 ], [ 25.756177738012184, 56.230796929334815 ], [ 25.800701280339808, 56.23524928392726 ], [ 25.838546291678028, 56.226344574742313 ], [ 25.876391303016192, 56.221892221049131 ], [ 25.896426897783101, 56.201856626282222 ], [ 25.905331606068728, 56.170690146832783 ], [ 25.94762897199945, 56.157333083954654 ], [ 25.976569275051986, 56.148428375668971 ], [ 25.976569275051986, 56.128392780902061 ], [ 25.98547398333767, 56.110583363431431 ], [ 26.012188109093927, 56.108357187034528 ], [ 26.025522902060288, 56.079419663786439 ], [ 26.007694533176164, 56.087274481931786 ], [ 25.989556105030204, 56.080530706926538 ], [ 25.982218051721702, 56.075879828791017 ], [ 25.954467808042864, 56.06288320609508 ], [ 25.941755405287722, 56.06172048611154 ], [ 25.926200798627917, 56.070686347396986 ], [ 25.915968866370065, 56.080065619832453 ], [ 25.83127119271137, 56.122853705334194 ], [ 25.834681838296206, 56.109624539540846 ], [ 25.838092482082345, 56.104276028515869 ], [ 25.850959913569113, 56.093139756894232 ], [ 25.853905470261225, 56.088669745912 ], [ 25.859796583645391, 56.081848456541024 ], [ 25.848789504132299, 56.074872138438423 ], [ 25.844603713090862, 56.065441189159515 ], [ 25.83762739408894, 56.058103135851013 ], [ 25.810600619922639, 56.04820709947802 ], [ 25.795717807730625, 56.040248928545168 ], [ 25.795496034000053, 56.040130340000061 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-036", "NAME_1": "Ilukstes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.961251118000064, 55.958458834000069 ], [ 25.871267944000067, 55.992138977000124 ], [ 25.795496034000053, 56.040130340000061 ], [ 25.795717807730625, 56.040248928545168 ], [ 25.810600619922639, 56.04820709947802 ], [ 25.83762739408894, 56.058103135851013 ], [ 25.844603713090862, 56.065441189159515 ], [ 25.848789504132299, 56.074872138438423 ], [ 25.859796583645391, 56.081848456541024 ], [ 25.853905470261225, 56.088669745912 ], [ 25.850959913569113, 56.093139756894232 ], [ 25.838092482082345, 56.104276028515869 ], [ 25.834681838296206, 56.109624539540846 ], [ 25.83127119271137, 56.122853705334194 ], [ 25.915968866370065, 56.080065619832453 ], [ 25.926200798627917, 56.070686347396986 ], [ 25.941755405287722, 56.06172048611154 ], [ 25.954467808042864, 56.06288320609508 ], [ 25.982218051721702, 56.075879828791017 ], [ 25.989556105030204, 56.080530706926538 ], [ 26.007694533176164, 56.087274481931786 ], [ 26.025522902060288, 56.079419663786439 ], [ 26.036995069566785, 56.068567613004916 ], [ 26.046296827636468, 56.06634552672466 ], [ 26.058905876704841, 56.067740789805555 ], [ 26.079369744817825, 56.078799547061408 ], [ 26.092392205935539, 56.089599920999547 ], [ 26.097559848907906, 56.096007799220558 ], [ 26.108566929320318, 56.101485501454704 ], [ 26.120504184820277, 56.100710354199464 ], [ 26.129340854896498, 56.098384915131703 ], [ 26.152440220439814, 56.109185289069785 ], [ 26.153060337164789, 56.118280341564457 ], [ 26.151355014822059, 56.121019191782239 ], [ 26.138952671328695, 56.131096096207841 ], [ 26.205821974304627, 56.163600571708969 ], [ 26.219671257722325, 56.166029365362874 ], [ 26.243390740889936, 56.167062893237926 ], [ 26.25718834836357, 56.162076118318168 ], [ 26.282199740925307, 56.158949693573447 ], [ 26.304989048106052, 56.153471992238622 ], [ 26.313050570927089, 56.150371405915621 ], [ 26.317546421230361, 56.14592723245579 ], [ 26.320698682598106, 56.141534735839343 ], [ 26.31863162594874, 56.134997667308483 ], [ 26.315065951632334, 56.130734361001942 ], [ 26.316306186880979, 56.122724514125025 ], [ 26.345348341752526, 56.093449815256747 ], [ 26.348399284599282, 56.073187567052571 ], [ 26.357025587061173, 56.055446681519129 ], [ 26.369639519448242, 56.043768621424704 ], [ 26.365489128420506, 56.02167389573367 ], [ 26.376556836628708, 55.997748114383967 ], [ 26.392967022310188, 55.977725237829418 ], [ 26.372580424846717, 55.953827283302473 ], [ 26.33783624264845, 55.965408677068751 ], [ 26.317982424249465, 55.950518313269527 ], [ 26.291510666683905, 55.940591404070005 ], [ 26.286547212084145, 55.924046555703967 ], [ 26.303092060450183, 55.917428616537393 ], [ 26.319636909715598, 55.907501707337872 ], [ 26.27662030288468, 55.881029949772312 ], [ 26.336181758081636, 55.839667827508208 ], [ 26.349417636414785, 55.809887100809021 ], [ 26.291510666683905, 55.806578130776074 ], [ 26.258420969052452, 55.804923646209261 ], [ 26.226905151551478, 55.796931254273943 ], [ 26.203754110000091, 55.827446187000064 ], [ 26.178639364000077, 55.849589539000092 ], [ 26.07942061300011, 55.898139547000071 ], [ 26.017615600000113, 55.937361959000029 ], [ 25.961251118000064, 55.958458834000069 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-DGV", "NAME_1": "Daugavpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.594531291000067, 55.666990866000063 ], [ 26.537480509000062, 55.669523010000077 ], [ 26.481049846000133, 55.678308005000062 ], [ 26.342350301000067, 55.716341858000092 ], [ 26.279718466000134, 55.743239441000085 ], [ 26.226905151551478, 55.796931254273943 ], [ 26.258420969052452, 55.804923646209261 ], [ 26.291510666683905, 55.806578130776074 ], [ 26.349417636414785, 55.809887100809021 ], [ 26.336181758081636, 55.839667827508208 ], [ 26.27662030288468, 55.881029949772312 ], [ 26.319636909715598, 55.907501707337872 ], [ 26.303092060450183, 55.917428616537393 ], [ 26.286547212084145, 55.924046555703967 ], [ 26.291510666683905, 55.940591404070005 ], [ 26.317982424249465, 55.950518313269527 ], [ 26.33783624264845, 55.965408677068751 ], [ 26.372580424846717, 55.953827283302473 ], [ 26.392967022310188, 55.977725237829418 ], [ 26.376556836628708, 55.997748114383967 ], [ 26.365489128420506, 56.02167389573367 ], [ 26.369639519448242, 56.043768621424704 ], [ 26.357025587061173, 56.055446681519129 ], [ 26.348399284599282, 56.073187567052571 ], [ 26.345348341752526, 56.093449815256747 ], [ 26.342609490635482, 56.107893377877076 ], [ 26.340749138661806, 56.134248359374283 ], [ 26.33656334762037, 56.149441229928811 ], [ 26.328295119224379, 56.162670397520799 ], [ 26.300803257064615, 56.18334096941021 ], [ 26.33542646695787, 56.189748846731845 ], [ 26.380850050789206, 56.196906032887057 ], [ 26.41655846540084, 56.183547674985164 ], [ 26.455367466335474, 56.180447089561483 ], [ 26.484512973994526, 56.167786362750405 ], [ 26.507508985851018, 56.132336331456486 ], [ 26.54084028455145, 56.133912461690727 ], [ 26.543940870874451, 56.142103175720933 ], [ 26.556549919942825, 56.156572578561622 ], [ 26.570657585778918, 56.153962917754427 ], [ 26.576238640800625, 56.14804596594854 ], [ 26.609621616344498, 56.132207140247317 ], [ 26.62062869585759, 56.1249466013046 ], [ 26.624814486899027, 56.121122545469063 ], [ 26.647913853341663, 56.135281887249278 ], [ 26.687342970101952, 56.12551504208551 ], [ 26.700365431219666, 56.118693752714478 ], [ 26.718503859365626, 56.111252345719151 ], [ 26.748166130962147, 56.111097316987582 ], [ 26.774727817135044, 56.115076402454008 ], [ 26.807283970378933, 56.127323717215745 ], [ 26.823251987289382, 56.12951996507428 ], [ 26.836791213243885, 56.129390773865111 ], [ 26.847643263126088, 56.12396474937367 ], [ 26.872292922280508, 56.12722036442824 ], [ 26.888467644765967, 56.128874009927586 ], [ 26.890793083833728, 56.105051174871789 ], [ 26.931100702435458, 56.058697415053643 ], [ 26.968669468121448, 56.060196030922043 ], [ 26.951926303955759, 56.041024074901088 ], [ 26.953631626298488, 56.034306139216937 ], [ 26.958282505333329, 56.027123115539325 ], [ 26.969444613578048, 56.013635566428206 ], [ 26.967739292134581, 56.007847804932226 ], [ 26.932650995147299, 56.003300279584209 ], [ 26.943503045029502, 55.966790880194935 ], [ 26.947120396189291, 55.960072944510728 ], [ 26.955647007003734, 55.950280260025977 ], [ 26.944484897859752, 55.941650296424029 ], [ 26.935596550940033, 55.932193508723401 ], [ 26.954561802285355, 55.927594306532001 ], [ 26.946345248934051, 55.911316230359716 ], [ 26.937818638119609, 55.90387482426371 ], [ 26.934356316590709, 55.892764391063793 ], [ 26.934201287859139, 55.873902493405296 ], [ 26.97797122619113, 55.864213161707994 ], [ 26.980606723621406, 55.855350654109372 ], [ 26.981536899608216, 55.844653632059419 ], [ 26.979986606896375, 55.838917548306199 ], [ 26.97874637164773, 55.827083644694426 ], [ 26.979056430909566, 55.826721910387846 ], [ 26.979425814849435, 55.826290963357224 ], [ 26.957816610000094, 55.81858368000006 ], [ 26.900145711000107, 55.778715312000045 ], [ 26.842784872000038, 55.719339092 ], [ 26.822837769000103, 55.706109925000092 ], [ 26.743049357000132, 55.68285553 ], [ 26.72010502100008, 55.681873678000031 ], [ 26.66687829600005, 55.693965963000025 ], [ 26.64010990400007, 55.695567933000049 ], [ 26.615615275000096, 55.687971497000078 ], [ 26.594531291000067, 55.666990866000063 ] ], [ [ 26.51841105512608, 55.826555912625224 ], [ 26.532466108823826, 55.832177934464085 ], [ 26.556359700739563, 55.837799956302888 ], [ 26.581658797215709, 55.840610967222347 ], [ 26.597119357272504, 55.85326051546042 ], [ 26.601817253763215, 55.873724676752374 ], [ 26.616709831693356, 55.881170965267813 ], [ 26.6249292322982, 55.888861395437004 ], [ 26.631927930929237, 55.897406317347645 ], [ 26.61117441097025, 55.903858709311976 ], [ 26.61679643280911, 55.923535784848582 ], [ 26.612579916429979, 55.94602387130459 ], [ 26.609768905510577, 55.958673419542663 ], [ 26.594919269308832, 55.962130279488804 ], [ 26.54110825823949, 55.92708533342136 ], [ 26.471864215361222, 55.933230279553754 ], [ 26.437917514181891, 55.914618231546115 ], [ 26.449310742796513, 55.90570709850374 ], [ 26.485769076881354, 55.892889715188176 ], [ 26.48748993591181, 55.868721074617895 ], [ 26.490300946831212, 55.85326051546042 ], [ 26.504356000528958, 55.840610967222347 ], [ 26.51841105512608, 55.826555912625224 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-024", "NAME_1": "Dagdas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 28.051100037936408, 56.140665997038127 ], [ 28.023798055000015, 56.12964996400008 ], [ 27.981009969000098, 56.118022767000028 ], [ 27.939668823000147, 56.113061829000074 ], [ 27.927059774000043, 56.109366964000074 ], [ 27.911453491000088, 56.100246074 ], [ 27.901531617000103, 56.089342346000038 ], [ 27.892746622000118, 56.077095032 ], [ 27.880654337000124, 56.063865866000114 ], [ 27.812544800000126, 56.03451365100004 ], [ 27.781228882000107, 56.016375224000015 ], [ 27.776991414000065, 55.99239736 ], [ 27.76006402003836, 55.975416227688804 ], [ 27.724729153427688, 55.99520500250884 ], [ 27.680720178893125, 56.008148818812913 ], [ 27.654832547184299, 56.021092635116986 ], [ 27.641888730880225, 56.015915108595379 ], [ 27.644477494141029, 55.987438713625693 ], [ 27.618589862432202, 55.990027476886496 ], [ 27.571992124636836, 55.997793765769643 ], [ 27.460675306220367, 56.036625214681862 ], [ 27.414077568425, 56.036625214681862 ], [ 27.38560117345537, 56.021092635116986 ], [ 27.367479830629634, 56.023681398377789 ], [ 27.3752461204121, 56.057335319869082 ], [ 27.370068593890494, 56.080634189216426 ], [ 27.315704566312661, 56.080634189216426 ], [ 27.263929301995688, 56.072867899433959 ], [ 27.240630433547665, 56.111699347446915 ], [ 27.307938276530194, 56.124643163750989 ], [ 27.339003435660004, 56.140175742416545 ], [ 27.331237145877537, 56.160885848503085 ], [ 27.300171987647104, 56.181595953690305 ], [ 27.295936313909749, 56.203029690267954 ], [ 27.339086134617389, 56.21026439078895 ], [ 27.412001580608433, 56.207163805365326 ], [ 27.420683221053764, 56.210574449151466 ], [ 27.447865024851012, 56.209385890746205 ], [ 27.44068200027408, 56.223674425534284 ], [ 27.447244907226661, 56.229901434803367 ], [ 27.466933628084462, 56.233467109119772 ], [ 27.488947788009966, 56.235017401831612 ], [ 27.508998244073666, 56.241115220790789 ], [ 27.523570997903221, 56.24824656852428 ], [ 27.528066847307116, 56.257393296963073 ], [ 27.535043166309038, 56.261114000011048 ], [ 27.548220656158321, 56.24824656852428 ], [ 27.551631300843837, 56.245481878985515 ], [ 27.568116081691812, 56.24230377919605 ], [ 27.651573521001183, 56.249745185292056 ], [ 27.667593214755016, 56.240236720748044 ], [ 27.687126905981927, 56.225095527036899 ], [ 27.701854689442371, 56.219488634492848 ], [ 27.722473586286981, 56.20664704052848 ], [ 27.743919305431575, 56.199774075213384 ], [ 27.778490837582069, 56.195639960116011 ], [ 27.810943638038452, 56.196260077740362 ], [ 27.822415806444269, 56.185666409377291 ], [ 27.838435500198159, 56.182307441535158 ], [ 27.852853224396767, 56.181945706329259 ], [ 27.88757978707747, 56.186648261308164 ], [ 27.898483513803114, 56.183237617522025 ], [ 27.913056267632612, 56.18370270371679 ], [ 28.00131961380913, 56.203288071787028 ], [ 28.020904981879369, 56.209644273164656 ], [ 28.031446974298433, 56.214476020252107 ], [ 28.039818556381249, 56.215690416179712 ], [ 28.048035108833176, 56.214398504987003 ], [ 28.052685987868017, 56.211065375566591 ], [ 28.056096632553533, 56.206957098890996 ], [ 28.06126427642522, 56.20354645420548 ], [ 28.0650883322607, 56.203288071787028 ], [ 28.070411003964637, 56.208507392502156 ], [ 28.077542351698128, 56.196725164834447 ], [ 28.06260786266273, 56.182100735060828 ], [ 28.04369428906017, 56.158019518485958 ], [ 28.047259963376575, 56.150888169853147 ], [ 28.050928988681846, 56.14112132379006 ], [ 28.051100037936408, 56.140665997038127 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-047", "NAME_1": "Kraslavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.76006402003836, 55.975416227688804 ], [ 27.74443526200011, 55.959737854000124 ], [ 27.645009806000104, 55.922840881000084 ], [ 27.617156209000115, 55.878554179000062 ], [ 27.610128214000014, 55.830960185000023 ], [ 27.601498251000066, 55.809617819000024 ], [ 27.592713256000081, 55.794244080000041 ], [ 27.564601278000055, 55.792228699000091 ], [ 27.438820842000069, 55.798739929000064 ], [ 27.405851277000124, 55.804346823000103 ], [ 27.374587036000037, 55.814837138000044 ], [ 27.349627319000035, 55.831218567000107 ], [ 27.329163452000074, 55.817575990000094 ], [ 27.282447957000073, 55.791866964000107 ], [ 27.263017619000095, 55.787216085000054 ], [ 27.235525757000033, 55.795846050000094 ], [ 27.173203980000096, 55.825740865000014 ], [ 27.151448201000079, 55.832458802000033 ], [ 27.110778849000042, 55.836282858000075 ], [ 26.981071004000086, 55.826877747000125 ], [ 26.979425814849435, 55.826290963357224 ], [ 26.979056430909566, 55.826721910387846 ], [ 26.97874637164773, 55.827083644694426 ], [ 26.979986606896375, 55.838917548306199 ], [ 26.981536899608216, 55.844653632059419 ], [ 26.980606723621406, 55.855350654109372 ], [ 26.97797122619113, 55.864213161707994 ], [ 26.934201287859139, 55.873902493405296 ], [ 26.934356316590709, 55.892764391063793 ], [ 26.937818638119609, 55.90387482426371 ], [ 26.946345248934051, 55.911316230359716 ], [ 26.954561802285355, 55.927594306532001 ], [ 26.935596550940033, 55.932193508723401 ], [ 26.944484897859752, 55.941650296424029 ], [ 26.955647007003734, 55.950280260025977 ], [ 26.947120396189291, 55.960072944510728 ], [ 26.943503045029502, 55.966790880194935 ], [ 26.932650995147299, 56.003300279584209 ], [ 26.967739292134581, 56.007847804932226 ], [ 26.999875454788366, 55.990027476886496 ], [ 27.041295666062126, 55.982261187104086 ], [ 27.103425983422369, 55.987438713625693 ], [ 27.147434957956932, 55.98484995036489 ], [ 27.194032695752298, 56.010737582073716 ], [ 27.188855169230635, 56.054746556608279 ], [ 27.263929301995688, 56.072867899433959 ], [ 27.315704566312661, 56.080634189216426 ], [ 27.370068593890494, 56.080634189216426 ], [ 27.3752461204121, 56.057335319869082 ], [ 27.367479830629634, 56.023681398377789 ], [ 27.38560117345537, 56.021092635116986 ], [ 27.414077568425, 56.036625214681862 ], [ 27.460675306220367, 56.036625214681862 ], [ 27.571992124636836, 55.997793765769643 ], [ 27.618589862432202, 55.990027476886496 ], [ 27.644477494141029, 55.987438713625693 ], [ 27.641888730880225, 56.015915108595379 ], [ 27.654832547184299, 56.021092635116986 ], [ 27.680720178893125, 56.008148818812913 ], [ 27.724729153427688, 55.99520500250884 ], [ 27.76006402003836, 55.975416227688804 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-066", "NAME_1": "Nicas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 20.971039259000065, 56.259670315000051 ], [ 20.98023522200009, 56.279282945000034 ], [ 20.982676629000082, 56.290757554000038 ], [ 20.982676629000082, 56.301459052000041 ], [ 20.981455925000091, 56.310492255000042 ], [ 20.970957879000082, 56.349839585000041 ], [ 20.968597852000073, 56.369818427000041 ], [ 20.970550977000073, 56.38898346600007 ], [ 20.979014519000089, 56.404364325000074 ], [ 20.992523634000065, 56.417914130000042 ], [ 20.998383009000065, 56.425685940000051 ], [ 21.003184441000087, 56.434800523000035 ], [ 21.005381707000083, 56.445868231000077 ], [ 21.019539950008664, 56.447081045853565 ], [ 21.022017119994075, 56.456989723097195 ], [ 21.02399885526296, 56.4713573046958 ], [ 21.035817904772443, 56.473944403311521 ], [ 21.04468834700009, 56.462103583000044 ], [ 21.030528191000087, 56.448431708000044 ], [ 21.037445509000065, 56.444647528000075 ], [ 21.04468834700009, 56.442206122000073 ], [ 21.04468834700009, 56.427964585000041 ], [ 21.04468834700009, 56.421128648000035 ], [ 21.030528191000087, 56.408107815000051 ], [ 21.053396030000044, 56.389960028000075 ], [ 21.061778191000087, 56.38703034100007 ], [ 21.070160352000073, 56.389349677000041 ], [ 21.077159050000091, 56.395209052000041 ], [ 21.07976321700005, 56.403062242000033 ], [ 21.068614129000082, 56.424058335000041 ], [ 21.066661004000082, 56.441839911000045 ], [ 21.06812584700009, 56.458807684000078 ], [ 21.072113477000073, 56.469549872000073 ], [ 21.067637566000087, 56.476385809000078 ], [ 21.063243035000085, 56.493963934000078 ], [ 21.128274935339959, 56.498784252890914 ], [ 21.147462973037761, 56.430560118554581 ], [ 21.211423098697026, 56.432692123342804 ], [ 21.226347127717759, 56.351675963874584 ], [ 21.179443035867394, 56.355939972551653 ], [ 21.183707044544462, 56.323959909722021 ], [ 21.179443035867394, 56.28771583821532 ], [ 21.16451900684666, 56.264263792739825 ], [ 21.109086897642214, 56.219491704778306 ], [ 21.085634852166663, 56.228019721233125 ], [ 21.083502847378497, 56.191775649726424 ], [ 21.083502847378497, 56.166191600362026 ], [ 21.055786793225934, 56.168323604250929 ], [ 21.034466751639286, 56.266395796628672 ], [ 20.971039259000065, 56.259670315000051 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-LPX", "NAME_1": "Liepāja" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 21.005381707000083, 56.445868231000077 ], [ 21.00554446700005, 56.465521552000041 ], [ 21.010590040000068, 56.475734768000052 ], [ 21.00326582100007, 56.492580471000053 ], [ 21.000498894000089, 56.510646877000056 ], [ 21.006358269000089, 56.519964911000045 ], [ 21.024261915000068, 56.510484117000033 ], [ 21.030528191000087, 56.500148830000057 ], [ 21.035817904772443, 56.473944403311521 ], [ 21.02399885526296, 56.4713573046958 ], [ 21.022017119994075, 56.456989723097195 ], [ 21.019539950008664, 56.447081045853565 ], [ 21.005381707000083, 56.445868231000077 ] ] ], [ [ [ 21.052907747694405, 56.511053778209259 ], [ 21.048106316000087, 56.515611070000034 ], [ 21.030528191000087, 56.524155992000033 ], [ 21.019541863000086, 56.527085679000038 ], [ 21.010508660000085, 56.527777411000045 ], [ 21.003103061000047, 56.530015367000033 ], [ 20.996348504000082, 56.537827867000033 ], [ 20.992198113000086, 56.548895575000074 ], [ 20.995127800000091, 56.55337148600006 ], [ 21.000336134000065, 56.557684637000079 ], [ 21.010508660000085, 56.598700262000079 ], [ 21.020642330455665, 56.615046011826529 ], [ 21.067247716508916, 56.605743150925662 ], [ 21.077412097989907, 56.562825341816051 ], [ 21.093761415756717, 56.568770547622648 ], [ 21.103220320461389, 56.532770547622647 ], [ 21.072706622012959, 56.523284245971126 ], [ 21.052907747694405, 56.511053778209259 ] ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-032", "NAME_1": "Grobinas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.063243035000085, 56.493963934000078 ], [ 21.05836022200009, 56.503648179000038 ], [ 21.052907747694405, 56.511053778209259 ], [ 21.072706622012959, 56.523284245971126 ], [ 21.103220320461389, 56.532770547622647 ], [ 21.093761415756717, 56.568770547622648 ], [ 21.077412097989907, 56.562825341816051 ], [ 21.067247716508916, 56.605743150925662 ], [ 21.020642330455665, 56.615046011826529 ], [ 21.046153190717177, 56.656195380312511 ], [ 21.05791879711478, 56.643760537119078 ], [ 21.121878922774044, 56.63523252066426 ], [ 21.153858985603677, 56.682136612514626 ], [ 21.211423098697026, 56.645892541907244 ], [ 21.258327190547391, 56.656552562250909 ], [ 21.281779236922205, 56.611780474289446 ], [ 21.254063182769642, 56.588328428813895 ], [ 21.256195186658545, 56.530764315720546 ], [ 21.347871366470372, 56.543556340852433 ], [ 21.354267379036287, 56.503048261567983 ], [ 21.384115437977073, 56.490256236436153 ], [ 21.386247441865919, 56.464672186172436 ], [ 21.437415542393353, 56.407108073079087 ], [ 21.416095500806705, 56.353807968662807 ], [ 21.379851429300004, 56.32822391839909 ], [ 21.26259119922446, 56.326091913610924 ], [ 21.226347127717759, 56.351675963874584 ], [ 21.211423098697026, 56.432692123342804 ], [ 21.147462973037761, 56.430560118554581 ], [ 21.128274935339959, 56.498784252890914 ], [ 21.063243035000085, 56.493963934000078 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-071", "NAME_1": "Pavilostas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.046153190717177, 56.656195380312511 ], [ 21.05836022200009, 56.688666083000044 ], [ 21.065196160000085, 56.774318752000056 ], [ 21.061208530000044, 56.794134833000044 ], [ 21.054209832000083, 56.81195709800005 ], [ 21.052744988000086, 56.828924872000073 ], [ 21.065196160000085, 56.84634023600006 ], [ 21.147227410000085, 56.87641022300005 ], [ 21.156748894000089, 56.885565497000073 ], [ 21.222911004000082, 56.907700914000031 ], [ 21.240977410000085, 56.91828034100007 ], [ 21.277598504000082, 56.949123440000051 ], [ 21.287771030000044, 56.955511786000045 ], [ 21.30014082100007, 56.959540106000077 ], [ 21.328187478569248, 56.97362864854324 ], [ 21.331752556635138, 56.97362864854324 ], [ 21.362603386636977, 56.966704007284079 ], [ 21.384772577092747, 56.956523749171652 ], [ 21.39583133344928, 56.942416083335559 ], [ 21.391490512776954, 56.934483750824427 ], [ 21.397226597429494, 56.929393622217844 ], [ 21.406063266606452, 56.923450832889614 ], [ 21.447301060295672, 56.912082018170565 ], [ 21.493396436796104, 56.903813787975935 ], [ 21.473707716837623, 56.887587389546354 ], [ 21.467971633084403, 56.878905748201703 ], [ 21.436449008614829, 56.867588609426775 ], [ 21.439547546282199, 56.83990492277394 ], [ 21.40969948824079, 56.831376905419802 ], [ 21.345739362581526, 56.820716885076138 ], [ 21.320155312317809, 56.807924859944308 ], [ 21.324419320994878, 56.758888763305777 ], [ 21.31589130364074, 56.739700725607975 ], [ 21.292439258165189, 56.756756759416874 ], [ 21.260459195335557, 56.739700725607975 ], [ 21.273251220467444, 56.707720662778343 ], [ 21.288175249488177, 56.682136612514626 ], [ 21.258327190547391, 56.656552562250909 ], [ 21.211423098697026, 56.645892541907244 ], [ 21.153858985603677, 56.682136612514626 ], [ 21.121878922774044, 56.63523252066426 ], [ 21.05791879711478, 56.643760537119078 ], [ 21.046153190717177, 56.656195380312511 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-VEN", "NAME_1": "Ventspils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.328187478569248, 56.97362864854324 ], [ 21.360118035000085, 56.98969147300005 ], [ 21.38249759200005, 57.008937893000052 ], [ 21.401621941000087, 57.037502346000053 ], [ 21.413584832000083, 57.073187567000048 ], [ 21.414805535000085, 57.113836981000077 ], [ 21.41187584700009, 57.12445709800005 ], [ 21.401052280000044, 57.151312567000048 ], [ 21.403168165000068, 57.162054755000042 ], [ 21.412608269000089, 57.176214911000045 ], [ 21.414805535000085, 57.184881903000075 ], [ 21.412608269000089, 57.250067450000074 ], [ 21.414805535000085, 57.270900783000059 ], [ 21.421397332000083, 57.288723049000055 ], [ 21.433604363000086, 57.306463934000078 ], [ 21.449473504000082, 57.320013739000046 ], [ 21.46656334700009, 57.325506903000075 ], [ 21.48023522200009, 57.333644924000055 ], [ 21.511533772406381, 57.378657806634251 ], [ 21.527651608135216, 57.358248103966787 ], [ 21.541985133307264, 57.340963559620548 ], [ 21.565593292096764, 57.342228281609891 ], [ 21.591730896663535, 57.360777549744114 ], [ 21.626367638655609, 57.35455976751922 ], [ 21.621475510186656, 57.389416618582061 ], [ 21.674789213251586, 57.400881341500678 ], [ 21.642327994241498, 57.45695071840089 ], [ 21.632631786327863, 57.466646927213901 ], [ 21.621669658996623, 57.473236291939322 ], [ 21.699229363000086, 57.55532461100006 ], [ 21.729665561000047, 57.573797919000071 ], [ 21.771657748000052, 57.586127020000049 ], [ 21.955902540000068, 57.592962958000044 ], [ 21.999278191000087, 57.600327867000033 ], [ 22.194509311000047, 57.657619533000059 ], [ 22.206427850165142, 57.626870428927134 ], [ 22.212939081173658, 57.620927638699527 ], [ 22.223791131055862, 57.623149725879102 ], [ 22.235625033768258, 57.619739081193586 ], [ 22.247097202174132, 57.599585273241757 ], [ 22.245391879831345, 57.588319811310214 ], [ 22.250352817228702, 57.576046658126756 ], [ 22.25903445767409, 57.564290269780088 ], [ 22.268491245374662, 57.55545359970381 ], [ 22.272056918791748, 57.549097399225559 ], [ 22.274227329127939, 57.517316393236911 ], [ 22.271901890060178, 57.507239487911988 ], [ 22.203792351835546, 57.483907579271317 ], [ 22.189529657267826, 57.476388657910206 ], [ 22.174491815444924, 57.471892809405574 ], [ 22.177282341606769, 57.465123195978663 ], [ 22.177437372136978, 57.459128729806991 ], [ 22.166688674142961, 57.45101553014257 ], [ 22.137853223947104, 57.437527981031451 ], [ 22.077960239073775, 57.423161932776964 ], [ 22.151650832320058, 57.404610094380303 ], [ 22.186429070944826, 57.401587023322463 ], [ 22.244926791837941, 57.407090563079009 ], [ 22.259573101217654, 57.397945550546694 ], [ 22.276707797826589, 57.387246813489639 ], [ 22.28213382321735, 57.381355699206154 ], [ 22.286009555896271, 57.351150825250329 ], [ 22.286009555896271, 57.334278468976152 ], [ 22.294897901916613, 57.321462714332768 ], [ 22.308850539021137, 57.288854885144815 ], [ 22.304199659986296, 57.286477770132933 ], [ 22.30140913292513, 57.276452542550771 ], [ 22.297068312252804, 57.271129869048195 ], [ 22.273762241134477, 57.265032050089019 ], [ 22.233092889125544, 57.243379625369414 ], [ 22.194025505772458, 57.212373765736686 ], [ 22.188134393287612, 57.198731187893998 ], [ 22.190304802724427, 57.195320543208481 ], [ 22.204102411097381, 57.19082469290521 ], [ 22.206427850165142, 57.177853909530256 ], [ 22.199606560794109, 57.166123358705988 ], [ 22.196350945739539, 57.157545071048162 ], [ 22.195575799383619, 57.153385118428446 ], [ 22.195110711390214, 57.149070136177841 ], [ 22.195265741021103, 57.132094428015421 ], [ 22.194335565034294, 57.128089505026594 ], [ 22.192475213060618, 57.124575507553629 ], [ 22.172734816258753, 57.116565659777336 ], [ 22.095168491232869, 57.101631170741882 ], [ 22.075324740744179, 57.101992905947839 ], [ 22.015276727139224, 57.113723455872787 ], [ 21.996466506324168, 57.107367255394479 ], [ 21.951973096681058, 57.098427232530753 ], [ 21.936573520551519, 57.097238675024812 ], [ 21.86009240114339, 57.099305732573498 ], [ 21.849860467086842, 57.098427232530753 ], [ 21.839938592292185, 57.093001207139991 ], [ 21.82190351693373, 57.085275580203813 ], [ 21.783094516898416, 57.085198065838028 ], [ 21.789502394220108, 57.095274970263631 ], [ 21.789812452582623, 57.099822496510967 ], [ 21.789192335857592, 57.104111640339909 ], [ 21.766971470357078, 57.107057197031963 ], [ 21.711574333988324, 57.116849881516771 ], [ 21.692350702023248, 57.114214383187232 ], [ 21.664083692608301, 57.116307278258262 ], [ 21.653851760350449, 57.116152249526692 ], [ 21.641449415957766, 57.114214383187232 ], [ 21.626256545403237, 57.104059964395788 ], [ 21.593028598590934, 57.087265123386715 ], [ 21.573804965726595, 57.085611476988049 ], [ 21.539026727101771, 57.07057363516509 ], [ 21.506160516394687, 57.075612087827551 ], [ 21.503008254127622, 57.058274645358551 ], [ 21.489210645754667, 57.051014106415835 ], [ 21.484869825981662, 57.028509020075205 ], [ 21.473552687206734, 57.0129544134154 ], [ 21.474947951187005, 56.986444404085944 ], [ 21.472002393595574, 56.971354885419601 ], [ 21.454329054342395, 56.960296129063067 ], [ 21.446680941772001, 56.949650783856555 ], [ 21.39583133344928, 56.942416083335559 ], [ 21.384772577092747, 56.956523749171652 ], [ 21.362603386636977, 56.966704007284079 ], [ 21.331752556635138, 56.97362864854324 ], [ 21.328187478569248, 56.97362864854324 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-VEN", "NAME_1": "Ventspils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.511533772406381, 57.378657806634251 ], [ 21.517751498000052, 57.387600002000056 ], [ 21.589121941000087, 57.438788153000075 ], [ 21.621669658996623, 57.473236291939322 ], [ 21.632631786327863, 57.466646927213901 ], [ 21.642327994241498, 57.45695071840089 ], [ 21.674789213251586, 57.400881341500678 ], [ 21.621475510186656, 57.389416618582061 ], [ 21.626367638655609, 57.35455976751922 ], [ 21.591730896663535, 57.360777549744114 ], [ 21.565593292096764, 57.342228281609891 ], [ 21.541985133307264, 57.340963559620548 ], [ 21.527651608135216, 57.358248103966787 ], [ 21.511533772406381, 57.378657806634251 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-027", "NAME_1": "Dundagas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.194509311000047, 57.657619533000059 ], [ 22.48373457100007, 57.74249909100007 ], [ 22.526133660000085, 57.749660549000055 ], [ 22.549082879000082, 57.75063711100006 ], [ 22.56031334700009, 57.752752997000073 ], [ 22.572601759000065, 57.756822007000039 ], [ 22.585785352000073, 57.759711005000042 ], [ 22.600271030000044, 57.758042710000041 ], [ 22.610362175000091, 57.754624742000033 ], [ 22.610118035000085, 57.753485419000071 ], [ 22.604991082000083, 57.751044012000079 ], [ 22.600271030000044, 57.743841864000046 ], [ 22.590830925000091, 57.706122137000079 ], [ 22.58716881558837, 57.66498444187846 ], [ 22.567978247198312, 57.674198927038844 ], [ 22.570264505502507, 57.617042483823525 ], [ 22.609130886780974, 57.578176102545058 ], [ 22.61141714418585, 57.541595979570786 ], [ 22.625134690413631, 57.502729598292319 ], [ 22.54740192785664, 57.498157082583248 ], [ 22.503963031768478, 57.461576959608976 ], [ 22.476527938413597, 57.413565546912423 ], [ 22.423944010907348, 57.420424320026314 ], [ 22.380505114819186, 57.411279289507604 ], [ 22.34849750665461, 57.411279289507604 ], [ 22.325634929908063, 57.434141866254095 ], [ 22.282196032920581, 57.427283093140204 ], [ 22.259573101217654, 57.397945550546694 ], [ 22.244926791837941, 57.407090563079009 ], [ 22.186429070944826, 57.401587023322463 ], [ 22.151650832320058, 57.404610094380303 ], [ 22.077960239073775, 57.423161932776964 ], [ 22.137853223947104, 57.437527981031451 ], [ 22.166688674142961, 57.45101553014257 ], [ 22.177437372136978, 57.459128729806991 ], [ 22.177282341606769, 57.465123195978663 ], [ 22.174491815444924, 57.471892809405574 ], [ 22.189529657267826, 57.476388657910206 ], [ 22.203792351835546, 57.483907579271317 ], [ 22.271901890060178, 57.507239487911988 ], [ 22.274227329127939, 57.517316393236911 ], [ 22.272056918791748, 57.549097399225559 ], [ 22.268491245374662, 57.55545359970381 ], [ 22.25903445767409, 57.564290269780088 ], [ 22.250352817228702, 57.576046658126756 ], [ 22.245391879831345, 57.588319811310214 ], [ 22.247097202174132, 57.599585273241757 ], [ 22.235625033768258, 57.619739081193586 ], [ 22.223791131055862, 57.623149725879102 ], [ 22.212939081173658, 57.620927638699527 ], [ 22.206427850165142, 57.626870428927134 ], [ 22.194509311000047, 57.657619533000059 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-079", "NAME_1": "Rojas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.58716881558837, 57.66498444187846 ], [ 22.590993686000047, 57.644761460000041 ], [ 22.600759311000047, 57.630113023000035 ], [ 22.656097852000073, 57.585760809000078 ], [ 22.878916863000086, 57.481268622000073 ], [ 22.950368686000047, 57.432806708000044 ], [ 22.994593607347898, 57.411893115396275 ], [ 22.949783286869149, 57.402134258988838 ], [ 22.933779482337229, 57.376985423938152 ], [ 22.894913101058762, 57.374699165633956 ], [ 22.846901689261529, 57.388416712761057 ], [ 22.824039111615718, 57.408993032102728 ], [ 22.741733834249032, 57.459290701304781 ], [ 22.705153710375441, 57.43642812455829 ], [ 22.677718617919879, 57.457004443899962 ], [ 22.693722422451799, 57.482153278950648 ], [ 22.625134690413631, 57.502729598292319 ], [ 22.61141714418585, 57.541595979570786 ], [ 22.609130886780974, 57.578176102545058 ], [ 22.570264505502507, 57.617042483823525 ], [ 22.567978247198312, 57.674198927038844 ], [ 22.58716881558837, 57.66498444187846 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-097", "NAME_1": "Talsi" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.994593607347898, 57.411893115396275 ], [ 23.031423372590552, 57.394476629792621 ], [ 23.006939729185149, 57.367840392520066 ], [ 22.968073347906682, 57.351836588887465 ], [ 22.920061936109448, 57.333546526950613 ], [ 22.954355801678901, 57.296966403976342 ], [ 23.018371018008054, 57.290107630862451 ], [ 23.054951141881702, 57.283248856849241 ], [ 23.061809914995536, 57.258100022697874 ], [ 23.080379673283289, 57.238470363916122 ], [ 23.039865350005869, 57.210203356299814 ], [ 22.977491896433833, 57.187052313913114 ], [ 22.967259963276661, 57.183538316440149 ], [ 22.948914828656427, 57.167880356992839 ], [ 22.946744419219556, 57.160335598109327 ], [ 22.941473422560364, 57.152454942441636 ], [ 22.930931431040676, 57.144574286773889 ], [ 22.911242710182933, 57.137184557521266 ], [ 22.831299269245847, 57.140078437369937 ], [ 22.80804487497096, 57.136616115841036 ], [ 22.801998731955905, 57.131345120081221 ], [ 22.793265414667133, 57.126022447477965 ], [ 22.76086429195351, 57.112328192791836 ], [ 22.669603713140816, 57.097703762118897 ], [ 22.6415434102002, 57.094732367904442 ], [ 22.649449904289611, 57.041660672402088 ], [ 22.635807326446923, 57.015848294163391 ], [ 22.591417271389957, 57.022385361794989 ], [ 22.559584587658549, 57.008381048746401 ], [ 22.508218215398301, 57.013832913458145 ], [ 22.480002882826739, 57.017760322081187 ], [ 22.412668490958083, 57.014763089445012 ], [ 22.382386101737211, 57.019672349998928 ], [ 22.372619255674067, 57.035769558118602 ], [ 22.362697380879411, 57.042409980336288 ], [ 22.35959679455641, 57.051453355987576 ], [ 22.359286737093214, 57.056465969328997 ], [ 22.363007440141246, 57.078738512572272 ], [ 22.354170770064968, 57.102664700415573 ], [ 22.348899774305096, 57.11010610651158 ], [ 22.327609083892071, 57.120415554034537 ], [ 22.297998488239614, 57.128761297695689 ], [ 22.262755160722065, 57.145013536345573 ], [ 22.249887730134617, 57.155271307924465 ], [ 22.24182620641426, 57.166691800386275 ], [ 22.234694857781449, 57.170541693744156 ], [ 22.206427850165142, 57.177853909530256 ], [ 22.204102411097381, 57.19082469290521 ], [ 22.190304802724427, 57.195320543208481 ], [ 22.188134393287612, 57.198731187893998 ], [ 22.194025505772458, 57.212373765736686 ], [ 22.233092889125544, 57.243379625369414 ], [ 22.273762241134477, 57.265032050089019 ], [ 22.297068312252804, 57.271129869048195 ], [ 22.30140913292513, 57.276452542550771 ], [ 22.304199659986296, 57.286477770132933 ], [ 22.308850539021137, 57.288854885144815 ], [ 22.294897901916613, 57.321462714332768 ], [ 22.286009555896271, 57.334278468976152 ], [ 22.286009555896271, 57.351150825250329 ], [ 22.28213382321735, 57.381355699206154 ], [ 22.276707797826589, 57.387246813489639 ], [ 22.259573101217654, 57.397945550546694 ], [ 22.282196032920581, 57.427283093140204 ], [ 22.325634929908063, 57.434141866254095 ], [ 22.34849750665461, 57.411279289507604 ], [ 22.380505114819186, 57.411279289507604 ], [ 22.423944010907348, 57.420424320026314 ], [ 22.476527938413597, 57.413565546912423 ], [ 22.503963031768478, 57.461576959608976 ], [ 22.54740192785664, 57.498157082583248 ], [ 22.625134690413631, 57.502729598292319 ], [ 22.693722422451799, 57.482153278950648 ], [ 22.677718617919879, 57.457004443899962 ], [ 22.705153710375441, 57.43642812455829 ], [ 22.741733834249032, 57.459290701304781 ], [ 22.824039111615718, 57.408993032102728 ], [ 22.846901689261529, 57.388416712761057 ], [ 22.894913101058762, 57.374699165633956 ], [ 22.933779482337229, 57.376985423938152 ], [ 22.949783286869149, 57.402134258988838 ], [ 22.994593607347898, 57.411893115396275 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-063", "NAME_1": "Mersraga" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.031423372590552, 57.394476629792621 ], [ 23.08334394600007, 57.378241278000075 ], [ 23.117523634000065, 57.373928127000056 ], [ 23.13021894600007, 57.370794989000046 ], [ 23.133148634000065, 57.362982489000046 ], [ 23.133555535000085, 57.353094794000071 ], [ 23.13803144600007, 57.343166408000059 ], [ 23.160151242985478, 57.318606927967323 ], [ 23.128025344294201, 57.312315985894031 ], [ 23.120842318817949, 57.305184638160483 ], [ 23.114641147970588, 57.29335073544803 ], [ 23.112780795996912, 57.265290432507413 ], [ 23.080379673283289, 57.238470363916122 ], [ 23.061809914995536, 57.258100022697874 ], [ 23.054951141881702, 57.283248856849241 ], [ 23.018371018008054, 57.290107630862451 ], [ 22.954355801678901, 57.296966403976342 ], [ 22.920061936109448, 57.333546526950613 ], [ 22.968073347906682, 57.351836588887465 ], [ 23.006939729185149, 57.367840392520066 ], [ 23.031423372590552, 57.394476629792621 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-029", "NAME_1": "Engures" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.160151242985478, 57.318606927967323 ], [ 23.164561394000089, 57.313666083000044 ], [ 23.174571160000085, 57.297349351000037 ], [ 23.182383660000085, 57.27765534100007 ], [ 23.190196160000085, 57.24249909100007 ], [ 23.198741082000083, 57.224188544000071 ], [ 23.213552280000044, 57.21625397300005 ], [ 23.22242272200009, 57.207017320000034 ], [ 23.250498894000089, 57.114447333000044 ], [ 23.260915561000047, 57.098944403000075 ], [ 23.274912957000083, 57.092718817000048 ], [ 23.29265384200005, 57.088934637000079 ], [ 23.340098504000082, 57.058579820000034 ], [ 23.422862175000091, 57.040228583000044 ], [ 23.508555535000085, 57.031236070000034 ], [ 23.52084394600007, 57.025580145000049 ], [ 23.56967207100007, 56.986273505000042 ], [ 23.578603263929494, 56.982092082883298 ], [ 23.575853306185138, 56.980527452280057 ], [ 23.499785597027653, 56.961381333781503 ], [ 23.483817580117204, 56.949650783856555 ], [ 23.473430617329086, 56.943475450531594 ], [ 23.475135938772553, 56.940684923470428 ], [ 23.477151320377118, 56.936473294007271 ], [ 23.442001124852027, 56.926539013064996 ], [ 23.372275396991938, 56.910448460827752 ], [ 23.277519921407247, 56.915811977940393 ], [ 23.200642837696762, 56.930114691439826 ], [ 23.148795501710993, 56.921175495952355 ], [ 23.141644144961276, 56.935478208552468 ], [ 23.177400928709858, 56.955144439164485 ], [ 23.197067159321932, 56.967659313926163 ], [ 23.168461732323067, 56.985537705800482 ], [ 23.177400928709858, 57.012355293162216 ], [ 23.21315771155912, 57.039172880524006 ], [ 23.231036103433382, 57.06599046788574 ], [ 23.204218516071649, 57.105322929109889 ], [ 23.150583341348124, 57.123201320984151 ], [ 23.113038718861787, 57.137504034483584 ], [ 23.10052384410011, 57.155382425458527 ], [ 23.120190075611504, 57.171472978595034 ], [ 23.132704949473805, 57.209017601081371 ], [ 23.129129271098975, 57.242986545192878 ], [ 23.112780795996912, 57.265290432507413 ], [ 23.114641147970588, 57.29335073544803 ], [ 23.120842318817949, 57.305184638160483 ], [ 23.128025344294201, 57.312315985894031 ], [ 23.160151242985478, 57.318606927967323 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-JUR", "NAME_1": "Jurmala" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.578603263929494, 56.982092082883298 ], [ 23.585134311000047, 56.979071356000077 ], [ 23.632009311000047, 56.970933335000041 ], [ 23.695160352000073, 56.967271226000037 ], [ 23.866058790000068, 56.990179755000042 ], [ 23.926931186000047, 57.006333726000037 ], [ 23.92869998531603, 57.005900580047694 ], [ 23.934281040337737, 56.996986396504951 ], [ 23.95445041645354, 56.991383791928399 ], [ 23.937232573024801, 56.973022831038804 ], [ 23.903263628913351, 56.962295795914201 ], [ 23.863931166789882, 56.955144439164485 ], [ 23.801356796578887, 56.9497809220519 ], [ 23.779902726329738, 56.956932278801617 ], [ 23.76738785246738, 56.942629565302184 ], [ 23.706601320994139, 56.931902530177581 ], [ 23.672632376882689, 56.928326851802751 ], [ 23.638663432771182, 56.928326851802751 ], [ 23.613633684147203, 56.931902530177581 ], [ 23.590391775160299, 56.92296333469011 ], [ 23.549271474299076, 56.924751173427865 ], [ 23.492034132569131, 56.934638780455316 ], [ 23.477151320377118, 56.936473294007271 ], [ 23.475135938772553, 56.940684923470428 ], [ 23.473430617329086, 56.943475450531594 ], [ 23.483817580117204, 56.949650783856555 ], [ 23.499785597027653, 56.961381333781503 ], [ 23.575853306185138, 56.980527452280057 ], [ 23.578603263929494, 56.982092082883298 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-RIX", "NAME_1": "Riga" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.926931186000047, 57.006333726000037 ], [ 23.95289147200009, 57.013251044000071 ], [ 23.991221550000091, 57.031236070000034 ], [ 24.009287957000083, 57.045599677000041 ], [ 24.021983269000089, 57.059027411000045 ], [ 24.035655144000089, 57.06899648600006 ], [ 24.074554884000065, 57.074937242000033 ], [ 24.113025617338337, 57.0887232661658 ], [ 24.15168379117398, 57.055587470185571 ], [ 24.162383007569701, 57.052823973147838 ], [ 24.181294385927174, 57.047939358514554 ], [ 24.224444206634814, 57.021636053860789 ], [ 24.249920688988595, 57.012644355052942 ], [ 24.274415316713487, 57.006365668041099 ], [ 24.287902865824606, 56.999880276353622 ], [ 24.302527297396864, 56.99099193033328 ], [ 24.320200636650043, 56.96902944635184 ], [ 24.299064975867907, 56.955541897240721 ], [ 24.258447299803038, 56.946007595174365 ], [ 24.246510044303079, 56.940969143411223 ], [ 24.234676140691306, 56.930401313469815 ], [ 24.236691522295928, 56.904123847237713 ], [ 24.258718295213157, 56.872992254289102 ], [ 24.197004022217811, 56.882058010468768 ], [ 24.155404494222012, 56.902392687372583 ], [ 24.132718539828716, 56.898361925062773 ], [ 24.112254672615052, 56.892625841309496 ], [ 24.104038120163125, 56.883608303180608 ], [ 24.099387241128284, 56.878492337051682 ], [ 24.088146223669241, 56.876134365908229 ], [ 24.070810174250141, 56.872497870880011 ], [ 24.066252713192171, 56.886115998854223 ], [ 24.062128533804753, 56.898439440327877 ], [ 24.040269402610818, 56.9100407990436 ], [ 24.026575147924746, 56.919626777054077 ], [ 24.002390577662993, 56.923709215308008 ], [ 23.97148807081777, 56.933243517374422 ], [ 23.942652622420553, 56.938669541865863 ], [ 23.94916385342907, 56.946782742429605 ], [ 23.988592970189416, 56.964688626578834 ], [ 23.994174025211066, 56.973163561449155 ], [ 24.000995313682779, 56.979261380408332 ], [ 23.992778761230852, 56.984532376168204 ], [ 23.967767367769795, 56.987684638435269 ], [ 23.95445041645354, 56.991383791928399 ], [ 23.934281040337737, 56.996986396504951 ], [ 23.92869998531603, 57.005900580047694 ], [ 23.926931186000047, 57.006333726000037 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-020", "NAME_1": "Carnikavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.113025617338337, 57.0887232661658 ], [ 24.210785352000073, 57.123724677000041 ], [ 24.219248894000089, 57.138006903000075 ], [ 24.238617384000065, 57.149562893000052 ], [ 24.261241082000083, 57.157456773000035 ], [ 24.278575066000087, 57.161037502000056 ], [ 24.278819207000083, 57.171779690000051 ], [ 24.28874759200005, 57.178900458000044 ], [ 24.312673373000052, 57.188299872000073 ], [ 24.330845177757187, 57.199754558993163 ], [ 24.34880651151127, 57.183827989869826 ], [ 24.341853109933083, 57.162967784235832 ], [ 24.337681068626409, 57.128200776344727 ], [ 24.320992904299089, 57.112903292153192 ], [ 24.26536569077399, 57.098996488996761 ], [ 24.258412289195746, 57.075354923091197 ], [ 24.247286846310885, 57.060057439798982 ], [ 24.23198936301867, 57.072573562819628 ], [ 24.162383007569701, 57.052823973147838 ], [ 24.15168379117398, 57.055587470185571 ], [ 24.113025617338337, 57.0887232661658 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-089", "NAME_1": "Saulkrastu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.330845177757187, 57.199754558993163 ], [ 24.379161004000082, 57.230210679000038 ], [ 24.400645379000082, 57.25812409100007 ], [ 24.408864780000044, 57.298163153000075 ], [ 24.405528191000087, 57.344468492000033 ], [ 24.404836854466101, 57.34807919696572 ], [ 24.40515669092855, 57.347895209296439 ], [ 24.410789421894265, 57.344665431764213 ], [ 24.425568882198149, 57.333968411512956 ], [ 24.429289585246124, 57.328542385222875 ], [ 24.432390170669805, 57.320222479983443 ], [ 24.430219761232991, 57.308388577270989 ], [ 24.426188998923124, 57.298828436782912 ], [ 24.426809115648155, 57.294590968898092 ], [ 24.42789432126591, 57.291387030686906 ], [ 24.45021853955393, 57.295831204146737 ], [ 24.461287311660101, 57.294765899626555 ], [ 24.476749102888789, 57.270050170878676 ], [ 24.48787454577365, 57.238064523259141 ], [ 24.444763455269253, 57.235283162088251 ], [ 24.372448077416834, 57.182437309734041 ], [ 24.34880651151127, 57.183827989869826 ], [ 24.330845177757187, 57.199754558993163 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-054", "NAME_1": "Limbaži" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.404836854466101, 57.34807919696572 ], [ 24.394893257192049, 57.400012509164952 ], [ 24.447544815540766, 57.407727525005271 ], [ 24.486483864738545, 57.416071606719242 ], [ 24.501781348930081, 57.427197049604104 ], [ 24.511516111679214, 57.448057254338778 ], [ 24.558799242590965, 57.452229295645452 ], [ 24.578268768089174, 57.464745418666098 ], [ 24.565752644169152, 57.474480181415231 ], [ 24.549064480741151, 57.489777664707447 ], [ 24.529594956142319, 57.521763312326982 ], [ 24.553236521148506, 57.541232836925872 ], [ 24.49065590604522, 57.544014198096761 ], [ 24.497609307623406, 57.555139640981622 ], [ 24.478139783024574, 57.567655764002268 ], [ 24.460060938561469, 57.564874402831379 ], [ 24.433638012384336, 57.567655764002268 ], [ 24.43502869252012, 57.588515968736942 ], [ 24.423903249635259, 57.589906648872727 ], [ 24.426684610806149, 57.620501616356478 ], [ 24.4183405281928, 57.670566109338438 ], [ 24.515688152086511, 57.678910191052466 ], [ 24.54072039812786, 57.666394068031764 ], [ 24.564361964033424, 57.673347469610007 ], [ 24.558799242590965, 57.688644953801543 ], [ 24.562971283897639, 57.717849240250189 ], [ 24.608863735572925, 57.715067879978676 ], [ 24.599128972823792, 57.752616249040614 ], [ 24.649193464906489, 57.770695093503718 ], [ 24.692304555410885, 57.760960331653962 ], [ 24.707602039602421, 57.749834888769101 ], [ 24.747931768935985, 57.731756044305996 ], [ 24.799386942053729, 57.733146724441724 ], [ 24.828591229401695, 57.719239921285293 ], [ 24.824419188095021, 57.699770395787084 ], [ 24.831372589673265, 57.666394068031764 ], [ 24.892562524640766, 57.652487264875333 ], [ 24.881437081755905, 57.624673657663152 ], [ 24.88004640162012, 57.582953247294483 ], [ 24.894945916021186, 57.567158312106415 ], [ 24.916184929590827, 57.555789496488046 ], [ 24.9329797705999, 57.55643545253406 ], [ 24.984346143759524, 57.563902696152468 ], [ 25.004344923879103, 57.562197373809738 ], [ 25.022328322394173, 57.555298570072921 ], [ 25.046409538969044, 57.535351467696046 ], [ 25.051370477265721, 57.524499416914523 ], [ 25.051370477265721, 57.518349921111962 ], [ 25.047029656593338, 57.51576610052507 ], [ 25.047804802949258, 57.514086615704741 ], [ 25.053850945964371, 57.511761175737661 ], [ 25.066718376551819, 57.51269135172447 ], [ 25.080826043287232, 57.510830999750794 ], [ 25.087078891877354, 57.507239487911988 ], [ 25.087802362289153, 57.503725491338344 ], [ 25.079585808937907, 57.500211493865322 ], [ 25.061912468785408, 57.498221951581797 ], [ 25.05323082923934, 57.491839911782449 ], [ 25.045634392613124, 57.482796536131218 ], [ 25.01256147633103, 57.476104437969411 ], [ 25.002949659898889, 57.467500311889864 ], [ 24.988841994062739, 57.461299140143183 ], [ 24.980315383248296, 57.452875882116246 ], [ 24.886884393200148, 57.420888170552587 ], [ 24.875877312787736, 57.41303335240724 ], [ 24.84668012918462, 57.412206529207936 ], [ 24.812677036016396, 57.419027818578911 ], [ 24.799499546167112, 57.417115789761851 ], [ 24.799964634160517, 57.411431382852015 ], [ 24.83164228736166, 57.395515041885631 ], [ 24.750713130106874, 57.396602082120353 ], [ 24.747931768935985, 57.410508885276784 ], [ 24.688132515003531, 57.389648680542166 ], [ 24.702039318159962, 57.349318950309282 ], [ 24.683960473696914, 57.328458745574608 ], [ 24.636677341885786, 57.320114662961316 ], [ 24.647802784770704, 57.300645138362427 ], [ 24.685351153832642, 57.286738335205996 ], [ 24.706211359466636, 57.242236563666438 ], [ 24.71309655106495, 57.227049872353689 ], [ 24.681573928394073, 57.223070786887263 ], [ 24.643281691396908, 57.237178452723413 ], [ 24.622146029715509, 57.265574653347528 ], [ 24.601527133770162, 57.278984687193542 ], [ 24.589124790276855, 57.285340888571113 ], [ 24.578066033920322, 57.28875153325663 ], [ 24.575430535590726, 57.29087026585006 ], [ 24.573725213247997, 57.295727851359231 ], [ 24.566283807151933, 57.310042222770335 ], [ 24.550005730979706, 57.316346747304522 ], [ 24.472232700378811, 57.293712469754666 ], [ 24.461287311660101, 57.294765899626555 ], [ 24.45021853955393, 57.295831204146737 ], [ 24.42789432126591, 57.291387030686906 ], [ 24.426809115648155, 57.294590968898092 ], [ 24.426188998923124, 57.298828436782912 ], [ 24.430219761232991, 57.308388577270989 ], [ 24.432390170669805, 57.320222479983443 ], [ 24.429289585246124, 57.328542385222875 ], [ 24.425568882198149, 57.333968411512956 ], [ 24.410789421894265, 57.344665431764213 ], [ 24.40515669092855, 57.347895209296439 ], [ 24.404836854466101, 57.34807919696572 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-094", "NAME_1": "Smiltenes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.311500278215249, 57.499332993822577 ], [ 26.311035191121164, 57.469774075013504 ], [ 26.325969679257298, 57.467603665576689 ], [ 26.312740512564574, 57.458663641813587 ], [ 26.304523960112647, 57.455356349915576 ], [ 26.296152378029831, 57.450007839789919 ], [ 26.293516879700235, 57.442695624003818 ], [ 26.300803257064615, 57.438974920955786 ], [ 26.308554722422457, 57.436158556372277 ], [ 26.315065951632334, 57.433135484415061 ], [ 26.318941685210575, 57.429001370217009 ], [ 26.320491977922416, 57.423782050401257 ], [ 26.315065951632334, 57.418097643491421 ], [ 26.312740512564574, 57.409726061408605 ], [ 26.309794955872519, 57.402207140047437 ], [ 26.302818637769917, 57.396135159509981 ], [ 26.299097934721885, 57.389468898870575 ], [ 26.301423373789646, 57.379831244915977 ], [ 26.302043491413997, 57.374844469096956 ], [ 26.286230503235117, 57.365826930968069 ], [ 26.277548861890466, 57.364715887827913 ], [ 26.276153598809515, 57.331022853921525 ], [ 26.277083774796324, 57.322961331100544 ], [ 26.280339389850951, 57.317871202493961 ], [ 26.27568851081611, 57.308336900427605 ], [ 26.264991488766157, 57.300947171174982 ], [ 26.266645135164822, 57.288854885144815 ], [ 26.261994256129981, 57.281930243885597 ], [ 26.130271029984044, 57.276478380073172 ], [ 26.101280551955881, 57.278157863994181 ], [ 26.075804071400739, 57.274127102583691 ], [ 26.058595819241646, 57.265290432507413 ], [ 26.043713006150313, 57.279966539124473 ], [ 26.027848342027369, 57.281646023045482 ], [ 26.012965528935979, 57.28782135726982 ], [ 25.991726515366395, 57.290017605128355 ], [ 25.910697869710873, 57.286736152551384 ], [ 25.904186638702413, 57.288441473994794 ], [ 25.891319207215645, 57.288312282785569 ], [ 25.880312126803176, 57.286296902080323 ], [ 25.769871138342353, 57.278712291539023 ], [ 25.754273309365715, 57.277641100056712 ], [ 25.745126580926922, 57.275108954514621 ], [ 25.736910027575675, 57.284669094103378 ], [ 25.733034294896754, 57.296606350502657 ], [ 25.738770378650031, 57.313065293828231 ], [ 25.736599969213216, 57.319395656784081 ], [ 25.740165642630302, 57.326423651730124 ], [ 25.753343133378905, 57.329679266784694 ], [ 25.751637811036119, 57.337689114560987 ], [ 25.722182245014551, 57.361305244041773 ], [ 25.704922316012073, 57.383784491960682 ], [ 25.713965691663304, 57.400992744119833 ], [ 25.667095167907632, 57.452514146910346 ], [ 25.657948438569576, 57.464606432041194 ], [ 25.675776808353021, 57.472719631705615 ], [ 25.680117629025347, 57.479592597020712 ], [ 25.718404072589863, 57.496556342401277 ], [ 25.776334139608821, 57.499687698128753 ], [ 25.835206685340211, 57.493516211204167 ], [ 25.896254182984137, 57.486190510983249 ], [ 25.935324581764007, 57.476422911288296 ], [ 25.959743581001419, 57.495958110678259 ], [ 25.959743581001419, 57.515493310068166 ], [ 25.945092181458961, 57.530144709610624 ], [ 25.97683688001797, 57.549679909000588 ], [ 26.050093877730262, 57.559447508695541 ], [ 26.094048076357637, 57.559447508695541 ], [ 26.123350875442554, 57.569215108390495 ], [ 26.155095574001507, 57.547238009526495 ], [ 26.184398373086424, 57.547238009526495 ], [ 26.240562070882845, 57.54235420967899 ], [ 26.24544587073035, 57.522819010289084 ], [ 26.282074370036128, 57.525260909763119 ], [ 26.301609569426091, 57.527702810136532 ], [ 26.311500278215249, 57.499332993822577 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-015", "NAME_1": "Balvu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.354899122796269, 57.284772446890884 ], [ 27.406265496855156, 57.263068346227158 ], [ 27.46073245633778, 57.274178779427075 ], [ 27.487087436935667, 57.273868720165297 ], [ 27.446236007285904, 57.243979191686321 ], [ 27.425730335986202, 57.212081481575069 ], [ 27.402946257463782, 57.159678099264852 ], [ 27.455349638874736, 57.114109941320692 ], [ 27.514588244651748, 57.079933823087458 ], [ 27.541929139418244, 57.07765541496542 ], [ 27.553321179129114, 57.038922480488054 ], [ 27.589775705484385, 57.04347929673213 ], [ 27.630787048083789, 57.038922480488054 ], [ 27.637622271550583, 57.018416809188352 ], [ 27.664963166317079, 56.988797506299875 ], [ 27.612559784006805, 56.984240690955119 ], [ 27.519145059996504, 56.954621388066585 ], [ 27.537372323174168, 56.934115716766883 ], [ 27.494082573352046, 56.91361004546718 ], [ 27.443957599163866, 56.915888453589218 ], [ 27.41661670439737, 56.902218005756311 ], [ 27.434843967575034, 56.883990742578703 ], [ 27.446701919957604, 56.852663522803311 ], [ 27.427814568787312, 56.85125885731037 ], [ 27.412156610239379, 56.84531606708282 ], [ 27.395723504436148, 56.835962633069016 ], [ 27.390917595770418, 56.831906033236805 ], [ 27.394948358080228, 56.828030301457204 ], [ 27.38342451283097, 56.821364040817798 ], [ 27.376603224359314, 56.820614731984278 ], [ 27.371332227700123, 56.822009995964493 ], [ 27.368696730269846, 56.824464627140799 ], [ 27.362495557623845, 56.824593818350024 ], [ 27.355209181158784, 56.821648260758593 ], [ 27.348077834324556, 56.815137031548716 ], [ 27.348387891787752, 56.807204698138264 ], [ 27.344822219269986, 56.801597804694893 ], [ 27.347922803794347, 56.784312039069334 ], [ 27.339861280973309, 56.782684231092389 ], [ 27.243174675870591, 56.789195462100849 ], [ 27.186072218957747, 56.793794664292307 ], [ 27.160750767134175, 56.81136465075798 ], [ 27.133155552186963, 56.808212389390235 ], [ 27.121373325418631, 56.81061534282378 ], [ 27.095586785601654, 56.80924591726523 ], [ 27.067526482661037, 56.805318509541564 ], [ 27.057914666228839, 56.800357571244888 ], [ 27.032283156042809, 56.801339423175818 ], [ 27.017555372582365, 56.806507066148129 ], [ 26.984017368306866, 56.805938626266538 ], [ 26.966188999422741, 56.809555976527065 ], [ 26.928465204105862, 56.831105048459165 ], [ 26.94401980986629, 56.840251776897958 ], [ 26.953324130104477, 56.846677123550364 ], [ 26.980761753252295, 56.865624905564914 ], [ 26.983707309944407, 56.870792548537281 ], [ 26.985102573025301, 56.878233953733968 ], [ 27.03612258399005, 56.883990742578703 ], [ 27.083969150056191, 56.874877110989871 ], [ 27.165991835254999, 56.861206664056283 ], [ 27.200167953488233, 56.861206664056283 ], [ 27.225230440132691, 56.895382782289516 ], [ 27.266241782732095, 56.915888453589218 ], [ 27.291304269376496, 56.927280493300088 ], [ 27.332315611975901, 56.93639412488892 ], [ 27.314088348798236, 56.979683874711043 ], [ 27.28674745403174, 56.972848651244249 ], [ 27.261684966488019, 56.972848651244249 ], [ 27.254849743021225, 56.993354322543951 ], [ 27.289025862153778, 57.000189546010688 ], [ 27.314088348798236, 57.025252032655146 ], [ 27.325480388509106, 57.061706559909794 ], [ 27.304974717209404, 57.06626337525455 ], [ 27.270798598076794, 57.063984967132512 ], [ 27.197889545366195, 57.07765541496542 ], [ 27.147764571178016, 57.08904745467629 ], [ 27.118145269188801, 57.084490638432214 ], [ 27.097639597889099, 57.091325861898952 ], [ 27.097639597889099, 57.114109941320692 ], [ 27.049793031822958, 57.111831533198654 ], [ 27.008103809043519, 57.114503838081646 ], [ 27.065046013962331, 57.155736395917927 ], [ 27.091245964929328, 57.170102444172414 ], [ 27.130209994595532, 57.199635525459087 ], [ 27.156564976092739, 57.226378078785274 ], [ 27.184366895715641, 57.228109239549724 ], [ 27.20111005988133, 57.234801336812211 ], [ 27.220488722376615, 57.23438792656151 ], [ 27.228911981302872, 57.238987127853648 ], [ 27.234182977062744, 57.242811183689128 ], [ 27.24100426643372, 57.246428534848974 ], [ 27.248135614167268, 57.248960680391065 ], [ 27.275885857846049, 57.246686917267368 ], [ 27.326011996656291, 57.277434394481702 ], [ 27.354899122796269, 57.284772446890884 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-096", "NAME_1": "Strencu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.729363815387728, 57.570143184460392 ], [ 25.710575685519473, 57.576405894116704 ], [ 25.724666782246175, 57.588931313429327 ], [ 25.707444330691317, 57.603022411055406 ], [ 25.694918910479316, 57.61554783036803 ], [ 25.66360536129838, 57.618679186095505 ], [ 25.630726133803989, 57.62024486350964 ], [ 25.591234165224535, 57.614106350227871 ], [ 25.571022764688848, 57.619461199674618 ], [ 25.533304885112329, 57.629454251312609 ], [ 25.509895461206554, 57.646197415478298 ], [ 25.530204298789329, 57.659064846065746 ], [ 25.600978262037131, 57.668780865144811 ], [ 25.600978262037131, 57.696963059497591 ], [ 25.611938004835054, 57.707922801396137 ], [ 25.621332069319521, 57.718882544194059 ], [ 25.67143374836877, 57.720448221608137 ], [ 25.688656200823004, 57.737670674062315 ], [ 25.735088788017151, 57.730380503357537 ], [ 25.847416184509314, 57.752357602221537 ], [ 25.82788098511935, 57.705961504120069 ], [ 25.857183784204267, 57.703519603746713 ], [ 25.859625684577679, 57.68398440435675 ], [ 25.989046380086336, 57.671774905187704 ], [ 25.979278780391382, 57.649797805424328 ], [ 25.959743581001419, 57.620495006339468 ], [ 25.932882681390595, 57.608285507170365 ], [ 25.876718983594174, 57.603401707322917 ], [ 25.901137982831642, 57.588750307780458 ], [ 25.903579882305678, 57.569215108390495 ], [ 25.888928482763276, 57.549679909000588 ], [ 25.896254182984137, 57.530144709610624 ], [ 25.945092181458961, 57.530144709610624 ], [ 25.959743581001419, 57.515493310068166 ], [ 25.959743581001419, 57.495958110678259 ], [ 25.935324581764007, 57.476422911288296 ], [ 25.896254182984137, 57.486190510983249 ], [ 25.835206685340211, 57.493516211204167 ], [ 25.776334139608821, 57.499687698128753 ], [ 25.821738785516516, 57.524738537653377 ], [ 25.809213366203892, 57.537263956966001 ], [ 25.779006690006725, 57.529797199880818 ], [ 25.743454912114487, 57.534132602137845 ], [ 25.734060847630019, 57.546658022349845 ], [ 25.771637106467267, 57.548223699763923 ], [ 25.78259684926519, 57.552920732006157 ], [ 25.752848977498275, 57.570143184460392 ], [ 25.729363815387728, 57.570143184460392 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-017", "NAME_1": "Beverinas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.630726133803989, 57.62024486350964 ], [ 25.66360536129838, 57.618679186095505 ], [ 25.694918910479316, 57.61554783036803 ], [ 25.707444330691317, 57.603022411055406 ], [ 25.724666782246175, 57.588931313429327 ], [ 25.710575685519473, 57.576405894116704 ], [ 25.729363815387728, 57.570143184460392 ], [ 25.752848977498275, 57.570143184460392 ], [ 25.78259684926519, 57.552920732006157 ], [ 25.771637106467267, 57.548223699763923 ], [ 25.734060847630019, 57.546658022349845 ], [ 25.743454912114487, 57.534132602137845 ], [ 25.779006690006725, 57.529797199880818 ], [ 25.809213366203892, 57.537263956966001 ], [ 25.821738785516516, 57.524738537653377 ], [ 25.776334139608821, 57.499687698128753 ], [ 25.718404072589863, 57.496556342401277 ], [ 25.680117629025347, 57.479592597020712 ], [ 25.675776808353021, 57.472719631705615 ], [ 25.657948438569576, 57.464606432041194 ], [ 25.602396275267893, 57.468637193451684 ], [ 25.561106804735232, 57.45101553014257 ], [ 25.547619255624113, 57.441558743341318 ], [ 25.526793654103813, 57.438199775499186 ], [ 25.519352248007806, 57.441248684079483 ], [ 25.509430373213149, 57.447010607153743 ], [ 25.500438674405302, 57.450137030999144 ], [ 25.474497104957436, 57.442540595272249 ], [ 25.420960320562301, 57.447449855826164 ], [ 25.398791131005851, 57.441248684079483 ], [ 25.392743157870427, 57.465242793220284 ], [ 25.378652061143669, 57.484030923088596 ], [ 25.377086383729591, 57.509081762613221 ], [ 25.395874512698583, 57.509081762613221 ], [ 25.431885095021073, 57.505950407785065 ], [ 25.452238902303463, 57.515344472269533 ], [ 25.456935934545697, 57.537263956966001 ], [ 25.480421096656244, 57.548223699763923 ], [ 25.507037613594946, 57.567011828732859 ], [ 25.536785485361861, 57.584234281187094 ], [ 25.569664712856195, 57.592062668257483 ], [ 25.591234165224535, 57.614106350227871 ], [ 25.630726133803989, 57.62024486350964 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-019", "NAME_1": "Burtnieku" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.552890252283305, 57.792390041766964 ], [ 25.564827507783264, 57.782649034125598 ], [ 25.596281229794897, 57.773681255485542 ], [ 25.616635037077288, 57.758024480445386 ], [ 25.660474006470224, 57.750196093374996 ], [ 25.688656200823004, 57.737670674062315 ], [ 25.67143374836877, 57.720448221608137 ], [ 25.621332069319521, 57.718882544194059 ], [ 25.611938004835054, 57.707922801396137 ], [ 25.600978262037131, 57.696963059497591 ], [ 25.600978262037131, 57.668780865144811 ], [ 25.530204298789329, 57.659064846065746 ], [ 25.509895461206554, 57.646197415478298 ], [ 25.533304885112329, 57.629454251312609 ], [ 25.571022764688848, 57.619461199674618 ], [ 25.591234165224535, 57.614106350227871 ], [ 25.569664712856195, 57.592062668257483 ], [ 25.536785485361861, 57.584234281187094 ], [ 25.507037613594946, 57.567011828732859 ], [ 25.480421096656244, 57.548223699763923 ], [ 25.456935934545697, 57.537263956966001 ], [ 25.43971348209152, 57.556052086834313 ], [ 25.41153128773874, 57.556052086834313 ], [ 25.378652061143669, 57.549789377178001 ], [ 25.352035544204966, 57.567011828732859 ], [ 25.31289360705432, 57.585799958601172 ], [ 25.278448702145852, 57.593628346570881 ], [ 25.269054637661384, 57.61554783036803 ], [ 25.231478377924759, 57.639032992478576 ], [ 25.225215668268447, 57.64842705786242 ], [ 25.198599151329745, 57.654689767518732 ], [ 25.189205086845277, 57.673477897387045 ], [ 25.156325859350886, 57.662518154589122 ], [ 25.121880955341794, 57.653124090104654 ], [ 25.093745150718121, 57.653122056737516 ], [ 25.085321892691184, 57.674619452725551 ], [ 25.092453241323994, 57.688236192146519 ], [ 25.082221307267503, 57.696891995069507 ], [ 25.075710077158305, 57.705935370720738 ], [ 25.07617516425239, 57.714513658378621 ], [ 25.080205925662938, 57.721231594062772 ], [ 25.087182244664859, 57.727174384290379 ], [ 25.090902947712834, 57.73125682254431 ], [ 25.098706089014797, 57.736011054366656 ], [ 25.083616571247774, 57.75414948251256 ], [ 25.077260369870146, 57.767171942730954 ], [ 25.017469133393774, 57.796470091421554 ], [ 25.034203016555978, 57.814388870050266 ], [ 25.06081953349468, 57.812823192636188 ], [ 25.092133083574936, 57.812823192636188 ], [ 25.112486889958006, 57.800297772424244 ], [ 25.128143664998106, 57.798732095010166 ], [ 25.13284069724034, 57.808126160393954 ], [ 25.123446632755872, 57.826914289362946 ], [ 25.112486889958006, 57.837874032160812 ], [ 25.142234762624184, 57.844136741817181 ], [ 25.157891536764964, 57.833176999918578 ], [ 25.223649990854369, 57.804994804666478 ], [ 25.245569475550838, 57.800297772424244 ], [ 25.267488960247306, 57.79560074018201 ], [ 25.270620315075462, 57.778378287727776 ], [ 25.31289360705432, 57.78307532086933 ], [ 25.378652061143669, 57.787772353111563 ], [ 25.455370257131619, 57.789338030525641 ], [ 25.494512194282322, 57.789338030525641 ], [ 25.552890252283305, 57.792390041766964 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-045", "NAME_1": "Kocenu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.916184929590827, 57.555789496488046 ], [ 24.894945916021186, 57.567158312106415 ], [ 24.88004640162012, 57.582953247294483 ], [ 24.881437081755905, 57.624673657663152 ], [ 24.93453006421106, 57.612039293578505 ], [ 24.966311069300389, 57.628420722538294 ], [ 24.991477492392335, 57.636482246258652 ], [ 25.000159132837666, 57.635862127734981 ], [ 25.005740186960054, 57.636120510153376 ], [ 25.019072707339546, 57.638135890858621 ], [ 25.024033643837583, 57.637360745402077 ], [ 25.027599318153989, 57.634260159079076 ], [ 25.03302534444407, 57.630177720825145 ], [ 25.042378777558497, 57.625836900152819 ], [ 25.061602411322212, 57.623950711556063 ], [ 25.067803582169574, 57.626457017777113 ], [ 25.084701775966153, 57.629764308775805 ], [ 25.083306511985938, 57.63410513034745 ], [ 25.075865105889932, 57.638704332538907 ], [ 25.063307732765622, 57.648109443396095 ], [ 25.062222528047187, 57.65239858722498 ], [ 25.067028435813654, 57.654982407811815 ], [ 25.093745150718121, 57.653122056737516 ], [ 25.121880955341794, 57.653124090104654 ], [ 25.156325859350886, 57.662518154589122 ], [ 25.189205086845277, 57.673477897387045 ], [ 25.198599151329745, 57.654689767518732 ], [ 25.225215668268447, 57.64842705786242 ], [ 25.231478377924759, 57.639032992478576 ], [ 25.269054637661384, 57.61554783036803 ], [ 25.278448702145852, 57.593628346570881 ], [ 25.31289360705432, 57.585799958601172 ], [ 25.352035544204966, 57.567011828732859 ], [ 25.378652061143669, 57.549789377178001 ], [ 25.372389350588037, 57.527869892481533 ], [ 25.377086383729591, 57.509081762613221 ], [ 25.378652061143669, 57.484030923088596 ], [ 25.392743157870427, 57.465242793220284 ], [ 25.398791131005851, 57.441248684079483 ], [ 25.404682245289393, 57.434995836388737 ], [ 25.402511834053882, 57.433548896464345 ], [ 25.391814812903249, 57.428045355808479 ], [ 25.398636102274281, 57.4111213244895 ], [ 25.397085808663121, 57.404532579115198 ], [ 25.392900017621685, 57.398202216159348 ], [ 25.39321007688352, 57.39068329479818 ], [ 25.386853874606629, 57.386058255084379 ], [ 25.35734663264094, 57.385076402254128 ], [ 25.348975050558124, 57.388900458089665 ], [ 25.334867384722031, 57.397323717015922 ], [ 25.328976271337808, 57.403447374396762 ], [ 25.322775098691807, 57.405488593523728 ], [ 25.301174350815643, 57.403679918393493 ], [ 25.272803988613191, 57.404920151843498 ], [ 25.238180779619256, 57.415048733112542 ], [ 25.21074059430299, 57.416650703567086 ], [ 25.196891309985972, 57.421844184061797 ], [ 25.184643996123498, 57.430086574935444 ], [ 25.159684278606562, 57.448250841503125 ], [ 25.140563999428991, 57.461764228136587 ], [ 25.140874057791507, 57.475019233250976 ], [ 25.123045688907382, 57.49494049720613 ], [ 25.097775913027931, 57.487964179103528 ], [ 25.079585808937907, 57.500211493865322 ], [ 25.087802362289153, 57.503725491338344 ], [ 25.087078891877354, 57.507239487911988 ], [ 25.080826043287232, 57.510830999750794 ], [ 25.066718376551819, 57.51269135172447 ], [ 25.053850945964371, 57.511761175737661 ], [ 25.047804802949258, 57.514086615704741 ], [ 25.047029656593338, 57.51576610052507 ], [ 25.051370477265721, 57.518349921111962 ], [ 25.051370477265721, 57.524499416914523 ], [ 25.046409538969044, 57.535351467696046 ], [ 25.022328322394173, 57.555298570072921 ], [ 25.004344923879103, 57.562197373809738 ], [ 24.984346143759524, 57.563902696152468 ], [ 24.9329797705999, 57.55643545253406 ], [ 24.916184929590827, 57.555789496488046 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-REZ", "NAME_1": "Rezeknes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.928465204105862, 56.831105048459165 ], [ 26.966188999422741, 56.809555976527065 ], [ 26.984017368306866, 56.805938626266538 ], [ 27.017555372582365, 56.806507066148129 ], [ 27.032283156042809, 56.801339423175818 ], [ 27.057914666228839, 56.800357571244888 ], [ 27.067526482661037, 56.805318509541564 ], [ 27.095586785601654, 56.80924591726523 ], [ 27.121373325418631, 56.81061534282378 ], [ 27.133155552186963, 56.808212389390235 ], [ 27.160750767134175, 56.81136465075798 ], [ 27.186072218957747, 56.793794664292307 ], [ 27.243174675870591, 56.789195462100849 ], [ 27.339861280973309, 56.782684231092389 ], [ 27.403722515381787, 56.784777781769208 ], [ 27.45549777969876, 56.771833965465134 ], [ 27.471030359263636, 56.740768807234645 ], [ 27.486312289680427, 56.693387356141557 ], [ 27.480266147564635, 56.680829983017247 ], [ 27.470344272769978, 56.674215400120602 ], [ 27.472049595112708, 56.663259996551574 ], [ 27.476390414885714, 56.658454087885843 ], [ 27.488017612023157, 56.653312283335197 ], [ 27.501815220396111, 56.655766912712807 ], [ 27.512408887859863, 56.654836738524637 ], [ 27.524811232252546, 56.6517878281457 ], [ 27.541554396418235, 56.645069892461549 ], [ 27.551631300843837, 56.636749986322798 ], [ 27.58020836772198, 56.619438382275519 ], [ 27.561449822851046, 56.599646307730893 ], [ 27.56113976358921, 56.575926826361922 ], [ 27.574317254337814, 56.556961575016658 ], [ 27.566565789879292, 56.541717026719368 ], [ 27.551166212850433, 56.535102443822723 ], [ 27.538453810095234, 56.53153677040558 ], [ 27.525121290615061, 56.529288844804341 ], [ 27.506827833737532, 56.52807444887668 ], [ 27.493753695776434, 56.520865586777404 ], [ 27.486777377673832, 56.514896959027396 ], [ 27.486002232217231, 56.508850816012341 ], [ 27.487087436935667, 56.499497381998538 ], [ 27.495459019018483, 56.488025214492041 ], [ 27.49638919410603, 56.477922472544037 ], [ 27.508688184811888, 56.460171617126377 ], [ 27.549925977601788, 56.478568426791412 ], [ 27.55938276530236, 56.464460760955319 ], [ 27.559692823664875, 56.457096870124417 ], [ 27.571681756008218, 56.442808336235714 ], [ 27.581603630802874, 56.433609930953537 ], [ 27.589665155422551, 56.428313096771944 ], [ 27.592145623221938, 56.424385688148959 ], [ 27.591060417604183, 56.421827704185148 ], [ 27.596486443894264, 56.418985501179918 ], [ 27.60253258601, 56.418572089130578 ], [ 27.607958612300081, 56.420484117048318 ], [ 27.62180789571778, 56.420690823522648 ], [ 27.620412631737565, 56.407151598467465 ], [ 27.623978306053971, 56.401079617030689 ], [ 27.62242801334213, 56.392475490951142 ], [ 27.640411410957825, 56.378781236265013 ], [ 27.670383741816238, 56.371804918162411 ], [ 27.689917433043092, 56.360616969697389 ], [ 27.689607374680577, 56.354700018790822 ], [ 27.687591993975332, 56.347258612694816 ], [ 27.690227492304928, 56.332375800502746 ], [ 27.698909132750259, 56.321988836815365 ], [ 27.711001417881107, 56.311136786933162 ], [ 27.715497267285059, 56.298476061021347 ], [ 27.686816846720092, 56.276306871464897 ], [ 27.686816846720092, 56.262870999197219 ], [ 27.694258253715418, 56.260752264805149 ], [ 27.698909132750259, 56.258065091430808 ], [ 27.700459426361419, 56.255016181051872 ], [ 27.692552931372688, 56.249822700557161 ], [ 27.683561231665465, 56.24726471569403 ], [ 27.667593214755016, 56.240236720748044 ], [ 27.651573521001183, 56.249745185292056 ], [ 27.568116081691812, 56.24230377919605 ], [ 27.551631300843837, 56.245481878985515 ], [ 27.548220656158321, 56.24824656852428 ], [ 27.535043166309038, 56.261114000011048 ], [ 27.528066847307116, 56.257393296963073 ], [ 27.523570997903221, 56.24824656852428 ], [ 27.508998244073666, 56.241115220790789 ], [ 27.488947788009966, 56.235017401831612 ], [ 27.466933628084462, 56.233467109119772 ], [ 27.447244907226661, 56.229901434803367 ], [ 27.44068200027408, 56.223674425534284 ], [ 27.447865024851012, 56.209385890746205 ], [ 27.420683221053764, 56.210574449151466 ], [ 27.412001580608433, 56.207163805365326 ], [ 27.339086134617389, 56.21026439078895 ], [ 27.295936313909749, 56.203029690267954 ], [ 27.173049757840033, 56.208300686027826 ], [ 27.162301059846016, 56.210781154726476 ], [ 27.150828892339518, 56.208429877236995 ], [ 27.125869174822526, 56.195846665691022 ], [ 27.102408074972686, 56.18907705316343 ], [ 27.099617547012144, 56.18638987888977 ], [ 27.097602166306899, 56.181997382273323 ], [ 27.09822228393125, 56.179568590418114 ], [ 27.101632927717446, 56.17075775786418 ], [ 27.07760338708664, 56.170912787495126 ], [ 27.054193963180865, 56.173315741827992 ], [ 27.044065382811141, 56.175951240157588 ], [ 27.038019239796085, 56.180111191877984 ], [ 27.034608595110569, 56.186648261308164 ], [ 27.035693800728325, 56.198120428814718 ], [ 27.034298536748054, 56.2077839220903 ], [ 27.028872512256612, 56.215432033761317 ], [ 27.015695020608689, 56.22416535105009 ], [ 27.001587354772596, 56.230573229271101 ], [ 26.99724653499959, 56.238583076148018 ], [ 26.988513217710818, 56.247936510161765 ], [ 26.986187777743737, 56.263232734403118 ], [ 26.95983279804517, 56.284032498401075 ], [ 26.949135775995273, 56.297752591508868 ], [ 26.946500277665677, 56.304315497562129 ], [ 26.944329868228806, 56.312945462063396 ], [ 26.950841099237323, 56.319663397747604 ], [ 26.975800815854939, 56.320128485741009 ], [ 26.991148716040414, 56.33149730135932 ], [ 26.966499057785256, 56.359066676985549 ], [ 26.951771275224132, 56.372631741361772 ], [ 26.89761437320476, 56.411311551087181 ], [ 26.899939813171841, 56.418468736343073 ], [ 26.910016716698124, 56.425083320139095 ], [ 26.914667595732965, 56.433403225378527 ], [ 26.925364617782861, 56.447614244002125 ], [ 26.992109165005957, 56.450827327419177 ], [ 27.033529377178979, 56.448238564158373 ], [ 27.041295666062126, 56.46635990698411 ], [ 27.007641744570833, 56.523312697822746 ], [ 27.00505298131003, 56.562144145835646 ], [ 26.981754112862006, 56.593209304066079 ], [ 26.9144462689801, 56.600975593848545 ], [ 26.883381110749667, 56.637218278600642 ], [ 26.852315952519177, 56.657928384687182 ], [ 26.808306977984614, 56.665694674469648 ], [ 26.772557406798853, 56.681010851069914 ], [ 26.782944369586971, 56.707391669190145 ], [ 26.907536247999417, 56.817979234554002 ], [ 26.928465204105862, 56.831105048459165 ] ], [ [ 27.339069016921599, 56.47490562749266 ], [ 27.355497216923027, 56.494984539005259 ], [ 27.360973283290377, 56.520539517784471 ], [ 27.365536672679298, 56.538793073541456 ], [ 27.363711316923741, 56.555221273542884 ], [ 27.340169016386994, 56.560760606316876 ], [ 27.310056339128721, 56.553347928164328 ], [ 27.274284355038958, 56.515546408040279 ], [ 27.284639408082171, 56.489658775432133 ], [ 27.339069016921599, 56.47490562749266 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-104", "NAME_1": "Vecpiebalgas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.745126580926922, 57.275108954514621 ], [ 25.754273309365715, 57.277641100056712 ], [ 25.769871138342353, 57.278712291539023 ], [ 25.760677364568721, 57.255442011639502 ], [ 25.752848977498275, 57.244482269740899 ], [ 25.745020589528565, 57.213168719660644 ], [ 25.771637106467267, 57.199077622933942 ], [ 25.763808719396877, 57.169329751167027 ], [ 25.774768461295423, 57.153672976126927 ], [ 25.804516333961601, 57.134884846258615 ], [ 25.83896123797075, 57.120793748632593 ], [ 25.862446400081296, 57.120793748632593 ], [ 25.881234529949552, 57.097308586522047 ], [ 25.923100213204236, 57.097445379700503 ], [ 25.907597284287249, 57.0700051952835 ], [ 25.906202020306978, 57.061426906726354 ], [ 25.899535759667572, 57.05016144479481 ], [ 25.896745232606406, 57.037552394827117 ], [ 25.895660027887971, 57.026261095373229 ], [ 25.904341668333302, 57.013962103768051 ], [ 25.911162956805015, 57.006753242568038 ], [ 25.919999626881236, 57.000086981928632 ], [ 25.923565301197698, 56.992955634195141 ], [ 25.920774774136476, 56.981896877838608 ], [ 25.917829217444421, 56.975230618098522 ], [ 25.886358269818288, 56.964223537686109 ], [ 25.854060499892114, 56.962569892186764 ], [ 25.850339796844139, 56.9663681104999 ], [ 25.844913770554058, 56.970269679801845 ], [ 25.841813185130377, 56.973835354118251 ], [ 25.837472365357371, 56.976780910810362 ], [ 25.833906691040966, 56.979726468401793 ], [ 25.829720899999529, 56.982413641776134 ], [ 25.822589553165301, 56.984997464161609 ], [ 25.813701206245639, 56.986754462448459 ], [ 25.796027866093141, 56.984997464161609 ], [ 25.787501255278698, 56.981690172263598 ], [ 25.779439731558341, 56.981018377795863 ], [ 25.769517856763684, 56.983498847393889 ], [ 25.754893426090746, 56.99329153097932 ], [ 25.727143182411908, 56.998691717948418 ], [ 25.705439080848862, 56.99367910370762 ], [ 25.69732588118444, 56.986134344824109 ], [ 25.629526401322323, 56.963086656124233 ], [ 25.63355716273287, 56.974868882892622 ], [ 25.639138217754521, 56.984480699324763 ], [ 25.636967808317706, 56.99021678307804 ], [ 25.638518101029547, 56.992955634195141 ], [ 25.638518101029547, 56.994764309325376 ], [ 25.634022250726275, 56.994712633381312 ], [ 25.602706332731032, 56.984377346537258 ], [ 25.582345819204193, 56.981896877838608 ], [ 25.565137567045042, 56.982103583413618 ], [ 25.517181837671671, 56.995126044531276 ], [ 25.50399548410428, 56.994820478282406 ], [ 25.510168968423159, 57.008064971221415 ], [ 25.492946516868244, 57.026853101089728 ], [ 25.507037613594946, 57.036247165574196 ], [ 25.528957098291414, 57.020590390534096 ], [ 25.547745228159727, 57.020590390534096 ], [ 25.58688716441111, 57.050338262300954 ], [ 25.616635037077288, 57.065995037341054 ], [ 25.618200714491365, 57.080086134967132 ], [ 25.594715552380819, 57.10983400673399 ], [ 25.564967680613961, 57.112965361562146 ], [ 25.541482518503415, 57.122359426945991 ], [ 25.563402002300563, 57.147410266470615 ], [ 25.572796067684351, 57.170895428581161 ], [ 25.575927422512507, 57.203774655176176 ], [ 25.560270647472407, 57.222562785044488 ], [ 25.575927422512507, 57.23665388177119 ], [ 25.599412584623053, 57.247613624569112 ], [ 25.600978262037131, 57.26483607702329 ], [ 25.638554521773756, 57.26483607702329 ], [ 25.669868070954692, 57.274230141507815 ], [ 25.709010008105395, 57.266401754437368 ], [ 25.745126580926922, 57.275108954514621 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-076", "NAME_1": "Raunas" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 25.657948438569576, 57.464606432041194 ], [ 25.667095167907632, 57.452514146910346 ], [ 25.713965691663304, 57.400992744119833 ], [ 25.704922316012073, 57.383784491960682 ], [ 25.722182245014551, 57.361305244041773 ], [ 25.751637811036119, 57.337689114560987 ], [ 25.753343133378905, 57.329679266784694 ], [ 25.740165642630302, 57.326423651730124 ], [ 25.736599969213216, 57.319395656784081 ], [ 25.738770378650031, 57.313065293828231 ], [ 25.733034294896754, 57.296606350502657 ], [ 25.736910027575675, 57.284669094103378 ], [ 25.745126580926922, 57.275108954514621 ], [ 25.709010008105395, 57.266401754437368 ], [ 25.669868070954692, 57.274230141507815 ], [ 25.638554521773756, 57.26483607702329 ], [ 25.600978262037131, 57.26483607702329 ], [ 25.597846907208975, 57.28518988340636 ], [ 25.579058777340663, 57.296149626204283 ], [ 25.555573615230117, 57.305543690688751 ], [ 25.547745228159727, 57.321200465728907 ], [ 25.533654130533705, 57.338422918183085 ], [ 25.513300323251315, 57.346251305253531 ], [ 25.511734645837237, 57.360342402879553 ], [ 25.546179550745649, 57.377564854434468 ], [ 25.575927422512507, 57.380696209262624 ], [ 25.602543939451209, 57.377564854434468 ], [ 25.605675294279365, 57.39635298430278 ], [ 25.626029101561755, 57.405747049686568 ], [ 25.649514263672302, 57.44019195369566 ], [ 25.657948438569576, 57.464606432041194 ] ] ], [ [ [ 25.991726515366395, 57.290017605128355 ], [ 26.012965528935979, 57.28782135726982 ], [ 26.027848342027369, 57.281646023045482 ], [ 26.043713006150313, 57.279966539124473 ], [ 26.058595819241646, 57.265290432507413 ], [ 26.058595819241646, 57.251286119458825 ], [ 26.049139032440394, 57.24999420826606 ], [ 26.01436079291625, 57.249865017056891 ], [ 26.008469678632764, 57.246738593211489 ], [ 26.004128858859758, 57.243844713362819 ], [ 26.013430616929384, 57.221055406182018 ], [ 25.989266276017815, 57.206906010004332 ], [ 25.945427306624879, 57.191249234964175 ], [ 25.893759949262233, 57.184986525307863 ], [ 25.868709109737608, 57.184986525307863 ], [ 25.849920980768616, 57.195946268105786 ], [ 25.81860743068836, 57.206906010004332 ], [ 25.798253623405969, 57.20847168741841 ], [ 25.771637106467267, 57.199077622933942 ], [ 25.745020589528565, 57.213168719660644 ], [ 25.752848977498275, 57.244482269740899 ], [ 25.760677364568721, 57.255442011639502 ], [ 25.769871138342353, 57.278712291539023 ], [ 25.880312126803176, 57.286296902080323 ], [ 25.891319207215645, 57.288312282785569 ], [ 25.904186638702413, 57.288441473994794 ], [ 25.910697869710873, 57.286736152551384 ], [ 25.991726515366395, 57.290017605128355 ] ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-075", "NAME_1": "Priekulu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.602396275267893, 57.468637193451684 ], [ 25.657948438569576, 57.464606432041194 ], [ 25.649514263672302, 57.44019195369566 ], [ 25.626029101561755, 57.405747049686568 ], [ 25.605675294279365, 57.39635298430278 ], [ 25.602543939451209, 57.377564854434468 ], [ 25.575927422512507, 57.380696209262624 ], [ 25.546179550745649, 57.377564854434468 ], [ 25.511734645837237, 57.360342402879553 ], [ 25.513300323251315, 57.346251305253531 ], [ 25.533654130533705, 57.338422918183085 ], [ 25.547745228159727, 57.321200465728907 ], [ 25.555573615230117, 57.305543690688751 ], [ 25.579058777340663, 57.296149626204283 ], [ 25.597846907208975, 57.28518988340636 ], [ 25.600978262037131, 57.26483607702329 ], [ 25.599412584623053, 57.247613624569112 ], [ 25.575927422512507, 57.23665388177119 ], [ 25.560270647472407, 57.222562785044488 ], [ 25.546179550745649, 57.213168719660644 ], [ 25.522694388635102, 57.20847168741841 ], [ 25.500774903938634, 57.216300075388119 ], [ 25.500774903938634, 57.24917930198319 ], [ 25.489815161140768, 57.263270399609212 ], [ 25.466329999030222, 57.277361496335971 ], [ 25.427188062778839, 57.289886916547914 ], [ 25.394308835284505, 57.302412335860595 ], [ 25.362995286103569, 57.296149626204283 ], [ 25.331681736922576, 57.291452593961992 ], [ 25.311327929640242, 57.296149626204283 ], [ 25.323853348952866, 57.308675045516907 ], [ 25.311327929640242, 57.31337207865846 ], [ 25.273751669903618, 57.318069110900694 ], [ 25.254963540035305, 57.325897497971141 ], [ 25.231478377924759, 57.330594530213375 ], [ 25.245569475550838, 57.347816982667609 ], [ 25.286277090115618, 57.360342402879553 ], [ 25.319156316710632, 57.355645369737999 ], [ 25.3567325764472, 57.357211047152077 ], [ 25.377086383729591, 57.369736467364078 ], [ 25.386853874606629, 57.386058255084379 ], [ 25.39321007688352, 57.39068329479818 ], [ 25.392900017621685, 57.398202216159348 ], [ 25.397085808663121, 57.404532579115198 ], [ 25.398636102274281, 57.4111213244895 ], [ 25.391814812903249, 57.428045355808479 ], [ 25.402511834053882, 57.433548896464345 ], [ 25.404682245289393, 57.434995836388737 ], [ 25.398791131005851, 57.441248684079483 ], [ 25.420960320562301, 57.447449855826164 ], [ 25.474497104957436, 57.442540595272249 ], [ 25.500438674405302, 57.450137030999144 ], [ 25.509430373213149, 57.447010607153743 ], [ 25.519352248007806, 57.441248684079483 ], [ 25.526793654103813, 57.438199775499186 ], [ 25.547619255624113, 57.441558743341318 ], [ 25.561106804735232, 57.45101553014257 ], [ 25.602396275267893, 57.468637193451684 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-070", "NAME_1": "Pargaujas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.079585808937907, 57.500211493865322 ], [ 25.097775913027931, 57.487964179103528 ], [ 25.123045688907382, 57.49494049720613 ], [ 25.140874057791507, 57.475019233250976 ], [ 25.140563999428991, 57.461764228136587 ], [ 25.159684278606562, 57.448250841503125 ], [ 25.184643996123498, 57.430086574935444 ], [ 25.196891309985972, 57.421844184061797 ], [ 25.21074059430299, 57.416650703567086 ], [ 25.238180779619256, 57.415048733112542 ], [ 25.272803988613191, 57.404920151843498 ], [ 25.301174350815643, 57.403679918393493 ], [ 25.322775098691807, 57.405488593523728 ], [ 25.328976271337808, 57.403447374396762 ], [ 25.334867384722031, 57.397323717015922 ], [ 25.348975050558124, 57.388900458089665 ], [ 25.35734663264094, 57.385076402254128 ], [ 25.386853874606629, 57.386058255084379 ], [ 25.377086383729591, 57.369736467364078 ], [ 25.3567325764472, 57.357211047152077 ], [ 25.319156316710632, 57.355645369737999 ], [ 25.286277090115618, 57.360342402879553 ], [ 25.245569475550838, 57.347816982667609 ], [ 25.231478377924759, 57.330594530213375 ], [ 25.231478377924759, 57.296149626204283 ], [ 25.231478377924759, 57.28518988340636 ], [ 25.206427538400135, 57.26483607702329 ], [ 25.165719924734731, 57.263270399609212 ], [ 25.131275019826262, 57.266401754437368 ], [ 25.090567405261538, 57.247613624569112 ], [ 25.048294114181999, 57.241350914912744 ], [ 24.980969982678516, 57.238219559185268 ], [ 24.933134800230846, 57.246221829273964 ], [ 24.939646030339986, 57.257978216721312 ], [ 24.942126499038693, 57.267512518787669 ], [ 24.935770297661065, 57.278261216781686 ], [ 24.915099724872391, 57.289371649981661 ], [ 24.898046502344187, 57.302600815775008 ], [ 24.888744745173824, 57.30921540047035 ], [ 24.889364861898855, 57.312212633106526 ], [ 24.900216912680378, 57.318930568790677 ], [ 24.902852411009917, 57.323116359832113 ], [ 24.903317498104059, 57.32771556292289 ], [ 24.880993279815982, 57.337224026567526 ], [ 24.878822870379167, 57.342934271899082 ], [ 24.87076134575949, 57.350556546047756 ], [ 24.854276564012196, 57.372725734704886 ], [ 24.83164228736166, 57.395515041885631 ], [ 24.799964634160517, 57.411431382852015 ], [ 24.799499546167112, 57.417115789761851 ], [ 24.812677036016396, 57.419027818578911 ], [ 24.84668012918462, 57.412206529207936 ], [ 24.875877312787736, 57.41303335240724 ], [ 24.886884393200148, 57.420888170552587 ], [ 24.980315383248296, 57.452875882116246 ], [ 24.988841994062739, 57.461299140143183 ], [ 25.002949659898889, 57.467500311889864 ], [ 25.01256147633103, 57.476104437969411 ], [ 25.045634392613124, 57.482796536131218 ], [ 25.05323082923934, 57.491839911782449 ], [ 25.061912468785408, 57.498221951581797 ], [ 25.079585808937907, 57.500211493865322 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-048", "NAME_1": "Krimuldas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.83164228736166, 57.395515041885631 ], [ 24.854276564012196, 57.372725734704886 ], [ 24.87076134575949, 57.350556546047756 ], [ 24.878822870379167, 57.342934271899082 ], [ 24.880993279815982, 57.337224026567526 ], [ 24.903317498104059, 57.32771556292289 ], [ 24.902852411009917, 57.323116359832113 ], [ 24.900216912680378, 57.318930568790677 ], [ 24.889364861898855, 57.312212633106526 ], [ 24.888744745173824, 57.30921540047035 ], [ 24.898046502344187, 57.302600815775008 ], [ 24.915099724872391, 57.289371649981661 ], [ 24.935770297661065, 57.278261216781686 ], [ 24.942126499038693, 57.267512518787669 ], [ 24.939646030339986, 57.257978216721312 ], [ 24.933134800230846, 57.246221829273964 ], [ 24.923471306955207, 57.236041572060856 ], [ 24.900371942311267, 57.218678290270873 ], [ 24.884218442926795, 57.199125473162042 ], [ 24.861967557157016, 57.189390711312228 ], [ 24.823028507959236, 57.182437309734041 ], [ 24.804949663496132, 57.165749145406721 ], [ 24.79660558088284, 57.142107579501157 ], [ 24.77991741745484, 57.135154177922971 ], [ 24.772964014977276, 57.147670300943616 ], [ 24.736806326950386, 57.147670300943616 ], [ 24.700648638024234, 57.137935538194483 ], [ 24.665881629233809, 57.125419415173837 ], [ 24.675616391083565, 57.149060981079401 ], [ 24.67422571094778, 57.169921186713395 ], [ 24.65753754751978, 57.179655948563152 ], [ 24.619989177558466, 57.176874588291582 ], [ 24.615817137151168, 57.203297514468716 ], [ 24.66031890779135, 57.210250916046903 ], [ 24.681573928394073, 57.223070786887263 ], [ 24.71309655106495, 57.227049872353689 ], [ 24.706211359466636, 57.242236563666438 ], [ 24.685351153832642, 57.286738335205996 ], [ 24.647802784770704, 57.300645138362427 ], [ 24.636677341885786, 57.320114662961316 ], [ 24.683960473696914, 57.328458745574608 ], [ 24.702039318159962, 57.349318950309282 ], [ 24.688132515003531, 57.389648680542166 ], [ 24.747931768935985, 57.410508885276784 ], [ 24.750713130106874, 57.396602082120353 ], [ 24.83164228736166, 57.395515041885631 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-033", "NAME_1": "Gulbene" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.130271029984044, 57.276478380073172 ], [ 26.261994256129981, 57.281930243885597 ], [ 26.266645135164822, 57.288854885144815 ], [ 26.264991488766157, 57.300947171174982 ], [ 26.27568851081611, 57.308336900427605 ], [ 26.280339389850951, 57.317871202493961 ], [ 26.277083774796324, 57.322961331100544 ], [ 26.276153598809515, 57.331022853921525 ], [ 26.359456007588676, 57.322547919051203 ], [ 26.423534784402761, 57.332159736382721 ], [ 26.430046013612639, 57.339704495266233 ], [ 26.430046013612639, 57.34600901980042 ], [ 26.424154901127793, 57.346990871731293 ], [ 26.411132440010078, 57.342856757533298 ], [ 26.404466180269992, 57.342236639909004 ], [ 26.399040154879231, 57.344019477516838 ], [ 26.394699334206905, 57.347145901362239 ], [ 26.393924187850985, 57.350711574779325 ], [ 26.397024774173985, 57.35427724909573 ], [ 26.421054314804792, 57.357946275300321 ], [ 26.43314659993564, 57.361408595929902 ], [ 26.446840854621712, 57.369160061287744 ], [ 26.474746127931439, 57.375051175571286 ], [ 26.479241978234711, 57.382389227980468 ], [ 26.480637241315605, 57.387401842221209 ], [ 26.475831332649875, 57.405075182373764 ], [ 26.52022138770684, 57.406470445454659 ], [ 26.525802442728548, 57.401871243263258 ], [ 26.531073439387683, 57.398874009727763 ], [ 26.544405958867912, 57.395618395572455 ], [ 26.567712029986183, 57.397427069803427 ], [ 26.594222040214959, 57.391303412422531 ], [ 26.61814822805826, 57.362493801547657 ], [ 26.6331860689819, 57.350298162729985 ], [ 26.677576124938184, 57.334330145819536 ], [ 26.681451856717786, 57.323684801512343 ], [ 26.682227003973026, 57.320015774408432 ], [ 26.714783156317594, 57.306476549353249 ], [ 26.747701042968743, 57.318956407212397 ], [ 26.7607235040864, 57.321669419907778 ], [ 26.790230746951408, 57.318413804853208 ], [ 26.836171094720214, 57.315313219429527 ], [ 26.840046828298455, 57.3162950713604 ], [ 26.845472852789896, 57.315933336154501 ], [ 26.849348586368137, 57.315261541686766 ], [ 26.865006544916071, 57.299526068773048 ], [ 26.905520868193491, 57.303737698236148 ], [ 26.988513217710818, 57.304409491804563 ], [ 27.020810987636935, 57.286736152551384 ], [ 27.059930046934085, 57.27275767702514 ], [ 27.066906365936006, 57.268701077192929 ], [ 27.060085076565031, 57.263223374958784 ], [ 27.048561232215093, 57.256427924009472 ], [ 27.046545850610471, 57.253353176108135 ], [ 27.045925733885497, 57.250304267527895 ], [ 27.056157667941989, 57.24870229797267 ], [ 27.156564976092739, 57.226378078785274 ], [ 27.130209994595532, 57.199635525459087 ], [ 27.091245964929328, 57.170102444172414 ], [ 27.065046013962331, 57.155736395917927 ], [ 27.008103809043519, 57.114503838081646 ], [ 26.995179478350224, 57.105145169114223 ], [ 26.979056430909566, 57.095585029525466 ], [ 26.953166538305084, 57.064837551411813 ], [ 26.929550408824298, 57.04979971048823 ], [ 26.901955193877086, 57.039257718069223 ], [ 26.860510694612856, 57.018018704499582 ], [ 26.868417188702267, 57.005900580047694 ], [ 26.871207716662752, 57.002644964993124 ], [ 26.860045606619451, 56.983007920978764 ], [ 26.795811801973059, 56.989131578359604 ], [ 26.78154910740534, 56.99254222304512 ], [ 26.757622917763399, 56.992955634195141 ], [ 26.734833612381294, 56.988201402372795 ], [ 26.725996942305017, 56.982620348250464 ], [ 26.703776075905182, 56.974197089324207 ], [ 26.697419875426874, 56.973473618912351 ], [ 26.691063674049303, 56.978202013212297 ], [ 26.635201449687145, 56.96499868404203 ], [ 26.603678827016211, 56.953604030901261 ], [ 26.568177117979587, 56.94841055040655 ], [ 26.559650506265825, 56.978899645202432 ], [ 26.544250929236966, 57.009233710367425 ], [ 26.532933791361359, 57.025253404121258 ], [ 26.512624952879264, 57.044063625835634 ], [ 26.497277052693789, 57.055794175760582 ], [ 26.46203372607556, 57.061271877994727 ], [ 26.369377883282652, 57.044632065717224 ], [ 26.328915235949353, 57.071865546357856 ], [ 26.321318801121777, 57.097910467693907 ], [ 26.27165774940562, 57.087110093755769 ], [ 26.21517540921775, 57.08912547536039 ], [ 26.183187696754771, 57.082614244351873 ], [ 26.116783481772245, 57.078066718104537 ], [ 26.133216586676099, 57.097083645393866 ], [ 26.139882847315505, 57.108452460112915 ], [ 26.161328565560837, 57.128838812960794 ], [ 26.165824415864108, 57.134238999929892 ], [ 26.168149854931869, 57.143437405212069 ], [ 26.131666293964258, 57.172014472090211 ], [ 26.102520786305206, 57.201263333436088 ], [ 26.089291619612538, 57.209893297037979 ], [ 26.076114128863935, 57.21472504322611 ], [ 26.062161492658731, 57.217179674402416 ], [ 26.013430616929384, 57.221055406182018 ], [ 26.004128858859758, 57.243844713362819 ], [ 26.008469678632764, 57.246738593211489 ], [ 26.01436079291625, 57.249865017056891 ], [ 26.049139032440394, 57.24999420826606 ], [ 26.058595819241646, 57.251286119458825 ], [ 26.058595819241646, 57.265290432507413 ], [ 26.075804071400739, 57.274127102583691 ], [ 26.101280551955881, 57.278157863994181 ], [ 26.130271029984044, 57.276478380073172 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-082", "NAME_1": "Rugaju" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.860045606619451, 56.983007920978764 ], [ 26.871207716662752, 57.002644964993124 ], [ 26.868417188702267, 57.005900580047694 ], [ 26.860510694612856, 57.018018704499582 ], [ 26.901955193877086, 57.039257718069223 ], [ 26.929550408824298, 57.04979971048823 ], [ 26.953166538305084, 57.064837551411813 ], [ 26.979056430909566, 57.095585029525466 ], [ 26.995179478350224, 57.105145169114223 ], [ 27.008103809043519, 57.114503838081646 ], [ 27.049793031822958, 57.111831533198654 ], [ 27.097639597889099, 57.114109941320692 ], [ 27.097639597889099, 57.091325861898952 ], [ 27.118145269188801, 57.084490638432214 ], [ 27.147764571178016, 57.08904745467629 ], [ 27.197889545366195, 57.07765541496542 ], [ 27.270798598076794, 57.063984967132512 ], [ 27.304974717209404, 57.06626337525455 ], [ 27.325480388509106, 57.061706559909794 ], [ 27.314088348798236, 57.025252032655146 ], [ 27.289025862153778, 57.000189546010688 ], [ 27.254849743021225, 56.993354322543951 ], [ 27.261684966488019, 56.972848651244249 ], [ 27.28674745403174, 56.972848651244249 ], [ 27.314088348798236, 56.979683874711043 ], [ 27.332315611975901, 56.93639412488892 ], [ 27.291304269376496, 56.927280493300088 ], [ 27.266241782732095, 56.915888453589218 ], [ 27.225230440132691, 56.895382782289516 ], [ 27.200167953488233, 56.861206664056283 ], [ 27.165991835254999, 56.861206664056283 ], [ 27.083969150056191, 56.874877110989871 ], [ 27.03612258399005, 56.883990742578703 ], [ 26.985102573025301, 56.878233953733968 ], [ 26.971149936820098, 56.890920518966766 ], [ 26.962468296374766, 56.913270576575826 ], [ 26.953941684661004, 56.920711981772513 ], [ 26.92396935380259, 56.931719062184925 ], [ 26.911411980678338, 56.939315497012558 ], [ 26.911101922315822, 56.947712918416414 ], [ 26.914822625363854, 56.957867337207801 ], [ 26.917148065330935, 56.969339504714355 ], [ 26.911877068671743, 56.973835354118251 ], [ 26.90257531060206, 56.977969469215623 ], [ 26.878184034765354, 56.978357041943923 ], [ 26.869812452682538, 56.976264146872836 ], [ 26.860045606619451, 56.983007920978764 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-039", "NAME_1": "Jaunpiebalgas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.013430616929384, 57.221055406182018 ], [ 26.062161492658731, 57.217179674402416 ], [ 26.076114128863935, 57.21472504322611 ], [ 26.089291619612538, 57.209893297037979 ], [ 26.102520786305206, 57.201263333436088 ], [ 26.131666293964258, 57.172014472090211 ], [ 26.168149854931869, 57.143437405212069 ], [ 26.165824415864108, 57.134238999929892 ], [ 26.161328565560837, 57.128838812960794 ], [ 26.139882847315505, 57.108452460112915 ], [ 26.133216586676099, 57.097083645393866 ], [ 26.116783481772245, 57.078066718104537 ], [ 26.089601677975054, 57.086128241824895 ], [ 26.077044304850745, 57.088221136895925 ], [ 26.064952019719897, 57.08871206331105 ], [ 26.056270379274565, 57.084422919482108 ], [ 26.052859734589049, 57.080262966862392 ], [ 26.048828973178558, 57.076774806911772 ], [ 26.038390334446376, 57.076852322176876 ], [ 26.022577345368177, 57.079513658028873 ], [ 25.949661900276453, 57.100390937291877 ], [ 25.923100213204236, 57.097445379700503 ], [ 25.881234529949552, 57.097308586522047 ], [ 25.862446400081296, 57.120793748632593 ], [ 25.83896123797075, 57.120793748632593 ], [ 25.804516333961601, 57.134884846258615 ], [ 25.774768461295423, 57.153672976126927 ], [ 25.763808719396877, 57.169329751167027 ], [ 25.771637106467267, 57.199077622933942 ], [ 25.798253623405969, 57.20847168741841 ], [ 25.81860743068836, 57.206906010004332 ], [ 25.849920980768616, 57.195946268105786 ], [ 25.868709109737608, 57.184986525307863 ], [ 25.893759949262233, 57.184986525307863 ], [ 25.945427306624879, 57.191249234964175 ], [ 25.989266276017815, 57.206906010004332 ], [ 26.013430616929384, 57.221055406182018 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-001", "NAME_1": "Aglonas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.038019239796085, 56.180111191877984 ], [ 27.044065382811141, 56.175951240157588 ], [ 27.054193963180865, 56.173315741827992 ], [ 27.07760338708664, 56.170912787495126 ], [ 27.101632927717446, 56.17075775786418 ], [ 27.09822228393125, 56.179568590418114 ], [ 27.097602166306899, 56.181997382273323 ], [ 27.099617547012144, 56.18638987888977 ], [ 27.102408074972686, 56.18907705316343 ], [ 27.125869174822526, 56.195846665691022 ], [ 27.150828892339518, 56.208429877236995 ], [ 27.162301059846016, 56.210781154726476 ], [ 27.173049757840033, 56.208300686027826 ], [ 27.295936313909749, 56.203029690267954 ], [ 27.300171987647104, 56.181595953690305 ], [ 27.331237145877537, 56.160885848503085 ], [ 27.339003435660004, 56.140175742416545 ], [ 27.307938276530194, 56.124643163750989 ], [ 27.240630433547665, 56.111699347446915 ], [ 27.263929301995688, 56.072867899433959 ], [ 27.188855169230635, 56.054746556608279 ], [ 27.194032695752298, 56.010737582073716 ], [ 27.147434957956932, 55.98484995036489 ], [ 27.103425983422369, 55.987438713625693 ], [ 27.041295666062126, 55.982261187104086 ], [ 26.999875454788366, 55.990027476886496 ], [ 26.967739292134581, 56.007847804932226 ], [ 26.969444613578048, 56.013635566428206 ], [ 26.958282505333329, 56.027123115539325 ], [ 26.953631626298488, 56.034306139216937 ], [ 26.951926303955759, 56.041024074901088 ], [ 26.968669468121448, 56.060196030922043 ], [ 26.931100702435458, 56.058697415053643 ], [ 26.890793083833728, 56.105051174871789 ], [ 26.888467644765967, 56.128874009927586 ], [ 26.928487338220066, 56.124239223361428 ], [ 26.936759761953454, 56.135820618027083 ], [ 26.920214913587358, 56.145747527226547 ], [ 26.931796307353693, 56.157328920992882 ], [ 26.968194974118717, 56.168910314759216 ], [ 26.999630187183357, 56.17056480022535 ], [ 27.038019239796085, 56.180111191877984 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-078", "NAME_1": "Riebinu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.925364617782861, 56.447614244002125 ], [ 26.914667595732965, 56.433403225378527 ], [ 26.910016716698124, 56.425083320139095 ], [ 26.899939813171841, 56.418468736343073 ], [ 26.89761437320476, 56.411311551087181 ], [ 26.951771275224132, 56.372631741361772 ], [ 26.966499057785256, 56.359066676985549 ], [ 26.991148716040414, 56.33149730135932 ], [ 26.975800815854939, 56.320128485741009 ], [ 26.950841099237323, 56.319663397747604 ], [ 26.944329868228806, 56.312945462063396 ], [ 26.946500277665677, 56.304315497562129 ], [ 26.949135775995273, 56.297752591508868 ], [ 26.95983279804517, 56.284032498401075 ], [ 26.986187777743737, 56.263232734403118 ], [ 26.988513217710818, 56.247936510161765 ], [ 26.99724653499959, 56.238583076148018 ], [ 27.001587354772596, 56.230573229271101 ], [ 27.015695020608689, 56.22416535105009 ], [ 27.028872512256612, 56.215432033761317 ], [ 27.034298536748054, 56.2077839220903 ], [ 27.035693800728325, 56.198120428814718 ], [ 27.034608595110569, 56.186648261308164 ], [ 27.038019239796085, 56.180111191877984 ], [ 26.999630187183357, 56.17056480022535 ], [ 26.968194974118717, 56.168910314759216 ], [ 26.931796307353693, 56.157328920992882 ], [ 26.920214913587358, 56.145747527226547 ], [ 26.936759761953454, 56.135820618027083 ], [ 26.928487338220066, 56.124239223361428 ], [ 26.888467644765967, 56.128874009927586 ], [ 26.882428558730965, 56.152052055358013 ], [ 26.861750720319492, 56.162390974563777 ], [ 26.826598394300447, 56.176865461271973 ], [ 26.801784988386487, 56.185136597176211 ], [ 26.834869530204628, 56.199611083884406 ], [ 26.839005097707059, 56.214085570592601 ], [ 26.814191691793098, 56.253373464114077 ], [ 26.783174933276541, 56.282322438429787 ], [ 26.741819256453482, 56.290593573434649 ], [ 26.702531362932064, 56.313339196047025 ], [ 26.665311253611435, 56.321610331051886 ], [ 26.65083676690324, 56.336084818659401 ], [ 26.669446821113866, 56.369169360477599 ], [ 26.710802498836244, 56.383643847185795 ], [ 26.733548121448621, 56.396050550592406 ], [ 26.685989092022965, 56.429135091511228 ], [ 26.623955576788433, 56.449812930822077 ], [ 26.590871034970291, 56.445677362420327 ], [ 26.576396548262096, 56.433270659912978 ], [ 26.559854277352997, 56.439474011616312 ], [ 26.537108654740621, 56.445677362420327 ], [ 26.537108654740621, 56.466355201731119 ], [ 26.540530226188991, 56.489678859991386 ], [ 26.559495476634936, 56.486164863417685 ], [ 26.59639244965183, 56.488800360847961 ], [ 26.597012567276124, 56.494536445500557 ], [ 26.59732262473932, 56.495569973375552 ], [ 26.593136833697884, 56.496603502149867 ], [ 26.593756952221554, 56.501590277968944 ], [ 26.595152215302448, 56.506551215366244 ], [ 26.627760043591138, 56.543732408323933 ], [ 26.706411574234721, 56.546083685813414 ], [ 26.722276239257042, 56.543112290699639 ], [ 26.743670281558252, 56.558847765411997 ], [ 26.771110466874518, 56.556083075873232 ], [ 26.815655551562429, 56.540011705275958 ], [ 26.826094191193931, 56.524172879574678 ], [ 26.775451288446163, 56.512003079178726 ], [ 26.800927769001305, 56.507248847356379 ], [ 26.807594027842072, 56.498489692545263 ], [ 26.82030643059727, 56.495518297431431 ], [ 26.833225538927479, 56.494794827019632 ], [ 26.847333204763572, 56.495518297431431 ], [ 26.85787519718258, 56.492546902317656 ], [ 26.865936720003617, 56.485751451368344 ], [ 26.902265252239602, 56.479162705994042 ], [ 26.952701450311679, 56.476914781292123 ], [ 26.958437534064899, 56.461101793113244 ], [ 26.949755893619567, 56.458156236421132 ], [ 26.933116082241384, 56.454926458888963 ], [ 26.925364617782861, 56.447614244002125 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-109", "NAME_1": "Vilanu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.833225538927479, 56.494794827019632 ], [ 26.82030643059727, 56.495518297431431 ], [ 26.807594027842072, 56.498489692545263 ], [ 26.800927769001305, 56.507248847356379 ], [ 26.775451288446163, 56.512003079178726 ], [ 26.826094191193931, 56.524172879574678 ], [ 26.815655551562429, 56.540011705275958 ], [ 26.771110466874518, 56.556083075873232 ], [ 26.743670281558252, 56.558847765411997 ], [ 26.729252557359644, 56.578303941373747 ], [ 26.736538933824704, 56.587295641080971 ], [ 26.749716423673988, 56.595822251895356 ], [ 26.788835483870457, 56.607061876304499 ], [ 26.792401157287543, 56.610291652937406 ], [ 26.788525424608622, 56.615485134331436 ], [ 26.779688755431721, 56.621453762081444 ], [ 26.777208285833694, 56.632615872124745 ], [ 26.780928988881726, 56.644966538774725 ], [ 26.778138461820561, 56.655921943243072 ], [ 26.77751834509553, 56.663725084544978 ], [ 26.771317173348848, 56.677471015175172 ], [ 26.772557406798853, 56.681010851069914 ], [ 26.808306977984614, 56.665694674469648 ], [ 26.852315952519177, 56.657928384687182 ], [ 26.883381110749667, 56.637218278600642 ], [ 26.9144462689801, 56.600975593848545 ], [ 26.981754112862006, 56.593209304066079 ], [ 27.00505298131003, 56.562144145835646 ], [ 27.007641744570833, 56.523312697822746 ], [ 27.041295666062126, 56.46635990698411 ], [ 27.033529377178979, 56.448238564158373 ], [ 26.992109165005957, 56.450827327419177 ], [ 26.925364617782861, 56.447614244002125 ], [ 26.933116082241384, 56.454926458888963 ], [ 26.949755893619567, 56.458156236421132 ], [ 26.958437534064899, 56.461101793113244 ], [ 26.952701450311679, 56.476914781292123 ], [ 26.902265252239602, 56.479162705994042 ], [ 26.865936720003617, 56.485751451368344 ], [ 26.85787519718258, 56.492546902317656 ], [ 26.847333204763572, 56.495518297431431 ], [ 26.833225538927479, 56.494794827019632 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-012", "NAME_1": "Babites" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.95445041645354, 56.991383791928399 ], [ 23.967767367769795, 56.987684638435269 ], [ 23.992778761230852, 56.984532376168204 ], [ 24.000995313682779, 56.979261380408332 ], [ 23.994174025211066, 56.973163561449155 ], [ 23.988592970189416, 56.964688626578834 ], [ 23.94916385342907, 56.946782742429605 ], [ 23.942652622420553, 56.938669541865863 ], [ 23.917566341513407, 56.928326851802751 ], [ 23.899687950538464, 56.914024139202638 ], [ 23.856779810040166, 56.897933586066131 ], [ 23.822810865928716, 56.883630873466018 ], [ 23.844264936177865, 56.847874089717436 ], [ 23.854991971302411, 56.826420019468287 ], [ 23.824598705565791, 56.803178110481383 ], [ 23.781690565067493, 56.821056502355646 ], [ 23.769639926641275, 56.800047511983053 ], [ 23.734086540761211, 56.822009995964493 ], [ 23.718428582213278, 56.835962633069016 ], [ 23.70726647306924, 56.852137356453795 ], [ 23.669542677752361, 56.874900825212876 ], [ 23.626237827413775, 56.8892410350457 ], [ 23.600813022802754, 56.889292710989764 ], [ 23.587635532054151, 56.891204738907561 ], [ 23.506761916029575, 56.894615384492397 ], [ 23.492034132569131, 56.934638780455316 ], [ 23.549271474299076, 56.924751173427865 ], [ 23.590391775160299, 56.92296333469011 ], [ 23.613633684147203, 56.931902530177581 ], [ 23.638663432771182, 56.928326851802751 ], [ 23.672632376882689, 56.928326851802751 ], [ 23.706601320994139, 56.931902530177581 ], [ 23.76738785246738, 56.942629565302184 ], [ 23.779902726329738, 56.956932278801617 ], [ 23.801356796578887, 56.9497809220519 ], [ 23.863931166789882, 56.955144439164485 ], [ 23.903263628913351, 56.962295795914201 ], [ 23.937232573024801, 56.973022831038804 ], [ 23.95445041645354, 56.991383791928399 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-031", "NAME_1": "Garkalnes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.320200636650043, 56.96902944635184 ], [ 24.302527297396864, 56.99099193033328 ], [ 24.287902865824606, 56.999880276353622 ], [ 24.274415316713487, 57.006365668041099 ], [ 24.249920688988595, 57.012644355052942 ], [ 24.224444206634814, 57.021636053860789 ], [ 24.181294385927174, 57.047939358514554 ], [ 24.162383007569701, 57.052823973147838 ], [ 24.23198936301867, 57.072573562819628 ], [ 24.247286846310885, 57.060057439798982 ], [ 24.261193650366636, 57.041978595335877 ], [ 24.290397936815339, 57.030853152451016 ], [ 24.320992904299089, 57.028071791280126 ], [ 24.362713314667701, 57.057276078628092 ], [ 24.407215086207259, 57.061448119934767 ], [ 24.458670258425684, 57.072573562819628 ], [ 24.501781348930081, 57.094824447690087 ], [ 24.508734750508324, 57.105949890574948 ], [ 24.514297471950727, 57.079526964397871 ], [ 24.550455160876936, 57.083699004805226 ], [ 24.565530100633623, 57.060001151232143 ], [ 24.546093907607712, 57.061024109375126 ], [ 24.521064159883053, 57.052084912988335 ], [ 24.53179119410828, 57.00202541663964 ], [ 24.451338432023022, 57.00202541663964 ], [ 24.401278935674327, 56.994874059889924 ], [ 24.395915417662366, 56.968056472528133 ], [ 24.371677488825014, 56.944417404939259 ], [ 24.352011258212997, 56.956932278801617 ], [ 24.320200636650043, 56.96902944635184 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-052", "NAME_1": "Kekavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.155404494222012, 56.902392687372583 ], [ 24.197004022217811, 56.882058010468768 ], [ 24.176803020542934, 56.86754032032951 ], [ 24.189317894405292, 56.851449768092323 ], [ 24.217923321404157, 56.828207859105362 ], [ 24.259043622265324, 56.824632180730532 ], [ 24.310890957351774, 56.824632180730532 ], [ 24.34198507914806, 56.851135707746664 ], [ 24.386241081260891, 56.842108465932938 ], [ 24.420367069484655, 56.839829503828526 ], [ 24.444017367807248, 56.837667955411803 ], [ 24.452182245214431, 56.837926336930877 ], [ 24.457763299336762, 56.837538764202577 ], [ 24.475850050639281, 56.832216092498641 ], [ 24.505305616660849, 56.818521836913192 ], [ 24.512695346812791, 56.817100735410577 ], [ 24.522978956813404, 56.795293281060026 ], [ 24.536983269861992, 56.777490749698302 ], [ 24.537603386587023, 56.770385240386531 ], [ 24.53310753808239, 56.759223131242493 ], [ 24.525459426411373, 56.75240184187146 ], [ 24.466432964409762, 56.763845649257291 ], [ 24.414585629323312, 56.776360523119592 ], [ 24.346647741100355, 56.787087558244195 ], [ 24.294800405114586, 56.78887539788127 ], [ 24.253680104253363, 56.78529971950644 ], [ 24.26261930064021, 56.762057809620217 ], [ 24.269770656490607, 56.745967257382972 ], [ 24.253680104253363, 56.738815900633256 ], [ 24.228650356528703, 56.751330775394933 ], [ 24.194681412417253, 56.740603740270387 ], [ 24.188165019300186, 56.717943050531346 ], [ 24.145576920538417, 56.728209824323926 ], [ 24.035563726984378, 56.720937509658313 ], [ 24.00812666231559, 56.709975491575676 ], [ 23.998204786621613, 56.721602687813743 ], [ 23.996344434647938, 56.730491034733461 ], [ 24.004357805068651, 56.737649103123601 ], [ 24.005170461247758, 56.762057809620217 ], [ 24.035563726984378, 56.767421327632121 ], [ 24.085623223333073, 56.762057809620217 ], [ 24.087411062070828, 56.779936201494479 ], [ 24.114228649432619, 56.801390271743628 ], [ 24.12316784581941, 56.821056502355646 ], [ 24.103501615207335, 56.835359215855078 ], [ 24.090986740445715, 56.846086250979681 ], [ 24.088146223669241, 56.876134365908229 ], [ 24.099387241128284, 56.878492337051682 ], [ 24.104038120163125, 56.883608303180608 ], [ 24.112254672615052, 56.892625841309496 ], [ 24.132718539828716, 56.898361925062773 ], [ 24.155404494222012, 56.902392687372583 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-062", "NAME_1": "Marupes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.942652622420553, 56.938669541865863 ], [ 23.97148807081777, 56.933243517374422 ], [ 24.002390577662993, 56.923709215308008 ], [ 24.026575147924746, 56.919626777054077 ], [ 24.040269402610818, 56.9100407990436 ], [ 24.062128533804753, 56.898439440327877 ], [ 24.066252713192171, 56.886115998854223 ], [ 24.035563726984378, 56.86754032032951 ], [ 24.012321817098154, 56.851449768092323 ], [ 23.967625838761421, 56.849661929354511 ], [ 23.885385237039031, 56.842510572604795 ], [ 23.854991971302411, 56.826420019468287 ], [ 23.844264936177865, 56.847874089717436 ], [ 23.822810865928716, 56.883630873466018 ], [ 23.856779810040166, 56.897933586066131 ], [ 23.899687950538464, 56.914024139202638 ], [ 23.917566341513407, 56.928326851802751 ], [ 23.942652622420553, 56.938669541865863 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-068", "NAME_1": "Olaines" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 24.066252713192171, 56.886115998854223 ], [ 24.070810174250141, 56.872497870880011 ], [ 24.088146223669241, 56.876134365908229 ], [ 24.090986740445715, 56.846086250979681 ], [ 24.103501615207335, 56.835359215855078 ], [ 24.12316784581941, 56.821056502355646 ], [ 24.114228649432619, 56.801390271743628 ], [ 24.087411062070828, 56.779936201494479 ], [ 24.085623223333073, 56.762057809620217 ], [ 24.035563726984378, 56.767421327632121 ], [ 24.005170461247758, 56.762057809620217 ], [ 24.004357805068651, 56.737649103123601 ], [ 23.996344434647938, 56.730491034733461 ], [ 23.941412388071228, 56.728579006815664 ], [ 23.935211216324547, 56.731240343566981 ], [ 23.924979282268055, 56.734108384993931 ], [ 23.919553256877293, 56.742144070292568 ], [ 23.912886997137207, 56.745425522869539 ], [ 23.900898064793864, 56.745838934918879 ], [ 23.828292678064599, 56.738139147303798 ], [ 23.818215772739677, 56.735503648074882 ], [ 23.811239454637132, 56.73255809228209 ], [ 23.801265902999035, 56.729535021224194 ], [ 23.783902622108315, 56.732093004288686 ], [ 23.767469517204461, 56.737932440829468 ], [ 23.756307407161103, 56.744133613475469 ], [ 23.764213901250514, 56.768266506893724 ], [ 23.759718051846619, 56.771883857154251 ], [ 23.769639926641275, 56.800047511983053 ], [ 23.781690565067493, 56.821056502355646 ], [ 23.824598705565791, 56.803178110481383 ], [ 23.854991971302411, 56.826420019468287 ], [ 23.885385237039031, 56.842510572604795 ], [ 23.967625838761421, 56.849661929354511 ], [ 24.012321817098154, 56.851449768092323 ], [ 24.035563726984378, 56.86754032032951 ], [ 24.066252713192171, 56.886115998854223 ] ] ], [ [ [ 24.230455055756181, 56.699422996873807 ], [ 24.209096307348659, 56.693464871406661 ], [ 24.195867139756672, 56.691527005067201 ], [ 24.184705030612633, 56.692560532942196 ], [ 24.179434034852818, 56.690441800348765 ], [ 24.173697951099541, 56.685119126846189 ], [ 24.172457716750216, 56.678504543949543 ], [ 24.160210401988422, 56.67010712344495 ], [ 24.148583204850979, 56.668065904317984 ], [ 24.131013218385306, 56.668272609892995 ], [ 24.117990757267592, 56.671373196215995 ], [ 24.08713992726581, 56.682070218265949 ], [ 24.033913202132567, 56.68821971316919 ], [ 24.000685256219583, 56.695635280843533 ], [ 24.00812666231559, 56.709975491575676 ], [ 24.035563726984378, 56.720937509658313 ], [ 24.145576920538417, 56.728209824323926 ], [ 24.188165019300186, 56.717943050531346 ], [ 24.20540844664248, 56.713786152908597 ], [ 24.230455055756181, 56.699422996873807 ] ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-087", "NAME_1": "Salaspils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.258718295213157, 56.872992254289102 ], [ 24.236691522295928, 56.904123847237713 ], [ 24.234676140691306, 56.930401313469815 ], [ 24.296588244751661, 56.921175495952355 ], [ 24.325193670851206, 56.915811977940393 ], [ 24.335920705975809, 56.896145747328376 ], [ 24.353799097850072, 56.910448460827752 ], [ 24.405646432936521, 56.926539013064996 ], [ 24.428888342822745, 56.924751173427865 ], [ 24.457349888186798, 56.886838079813515 ], [ 24.439831576765812, 56.887484035859529 ], [ 24.435645785724432, 56.884021715229949 ], [ 24.437816196060567, 56.878233953733968 ], [ 24.444482455800653, 56.872368678771466 ], [ 24.448203158848685, 56.86554739029981 ], [ 24.447583042123654, 56.857563381844557 ], [ 24.442932163088813, 56.845290229560419 ], [ 24.444017367807248, 56.837667955411803 ], [ 24.420367069484655, 56.839829503828526 ], [ 24.386241081260891, 56.842108465932938 ], [ 24.34198507914806, 56.851135707746664 ], [ 24.310890957351774, 56.824632180730532 ], [ 24.259043622265324, 56.824632180730532 ], [ 24.217923321404157, 56.828207859105362 ], [ 24.189317894405292, 56.851449768092323 ], [ 24.176803020542934, 56.86754032032951 ], [ 24.197004022217811, 56.882058010468768 ], [ 24.258718295213157, 56.872992254289102 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-095", "NAME_1": "Stopinu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.234676140691306, 56.930401313469815 ], [ 24.246510044303079, 56.940969143411223 ], [ 24.258447299803038, 56.946007595174365 ], [ 24.299064975867907, 56.955541897240721 ], [ 24.320200636650043, 56.96902944635184 ], [ 24.352011258212997, 56.956932278801617 ], [ 24.371677488825014, 56.944417404939259 ], [ 24.405646432936521, 56.926539013064996 ], [ 24.353799097850072, 56.910448460827752 ], [ 24.335920705975809, 56.896145747328376 ], [ 24.325193670851206, 56.915811977940393 ], [ 24.296588244751661, 56.921175495952355 ], [ 24.234676140691306, 56.930401313469815 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-090", "NAME_1": "Sejas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.550005730979706, 57.316346747304522 ], [ 24.566283807151933, 57.310042222770335 ], [ 24.573725213247997, 57.295727851359231 ], [ 24.575430535590726, 57.29087026585006 ], [ 24.578066033920322, 57.28875153325663 ], [ 24.589124790276855, 57.285340888571113 ], [ 24.601527133770162, 57.278984687193542 ], [ 24.622146029715509, 57.265574653347528 ], [ 24.643281691396908, 57.237178452723413 ], [ 24.681573928394073, 57.223070786887263 ], [ 24.66031890779135, 57.210250916046903 ], [ 24.615817137151168, 57.203297514468716 ], [ 24.619989177558466, 57.176874588291582 ], [ 24.65753754751978, 57.179655948563152 ], [ 24.67422571094778, 57.169921186713395 ], [ 24.675616391083565, 57.149060981079401 ], [ 24.665881629233809, 57.125419415173837 ], [ 24.618598497422681, 57.132372816752081 ], [ 24.568534005340041, 57.137935538194483 ], [ 24.553236521148506, 57.124028735038053 ], [ 24.508734750508324, 57.105949890574948 ], [ 24.467014340139656, 57.182437309734041 ], [ 24.425293929771044, 57.181046628698937 ], [ 24.394698962287237, 57.178265268427367 ], [ 24.372448077416834, 57.182437309734041 ], [ 24.444763455269253, 57.235283162088251 ], [ 24.48787454577365, 57.238064523259141 ], [ 24.476749102888789, 57.270050170878676 ], [ 24.461287311660101, 57.294765899626555 ], [ 24.472232700378811, 57.293712469754666 ], [ 24.550005730979706, 57.316346747304522 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-061", "NAME_1": "Malpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.984036086296328, 57.079927070078213 ], [ 24.9893070820562, 57.060600084426312 ], [ 25.01070112525673, 57.049437975282331 ], [ 25.040363396853252, 57.037914130033073 ], [ 25.048114862211094, 57.029103299277836 ], [ 25.055711297038727, 57.0249433457588 ], [ 25.063152704034053, 57.024245713768664 ], [ 25.092143182062159, 57.016003322895017 ], [ 25.100721469720042, 57.009802151148335 ], [ 25.125991244700174, 57.001585597797089 ], [ 25.147540316632274, 56.989389959878736 ], [ 25.138083529831022, 56.980062364286653 ], [ 25.142579380134293, 56.950787665418375 ], [ 25.036332635442761, 56.933992825308621 ], [ 25.000159132837666, 56.939573880330272 ], [ 24.976594679301002, 56.935413926811236 ], [ 24.954838900894515, 56.940607408205324 ], [ 24.938250767259092, 56.939108792336867 ], [ 24.918820427920366, 56.934638780455316 ], [ 24.890450066617291, 56.922675686533694 ], [ 24.877272576768007, 56.921487128128433 ], [ 24.840323927806992, 56.926034654375769 ], [ 24.807406040256524, 56.933889472521116 ], [ 24.816057621761843, 56.966268633790378 ], [ 24.817845460499598, 56.989510542777282 ], [ 24.845279392829696, 56.984960700775673 ], [ 24.889781164369197, 57.011383627852126 ], [ 24.88004640162012, 57.037806554029203 ], [ 24.888390483334149, 57.055885398492308 ], [ 24.86613959846369, 57.093433767554302 ], [ 24.878655721484336, 57.104559210439163 ], [ 24.89951592621901, 57.087871046111843 ], [ 24.93567361514522, 57.080917644533656 ], [ 24.984036086296328, 57.079927070078213 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-091", "NAME_1": "Siguldas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.147540316632274, 56.989389959878736 ], [ 25.125991244700174, 57.001585597797089 ], [ 25.100721469720042, 57.009802151148335 ], [ 25.092143182062159, 57.016003322895017 ], [ 25.063152704034053, 57.024245713768664 ], [ 25.055711297038727, 57.0249433457588 ], [ 25.048114862211094, 57.029103299277836 ], [ 25.040363396853252, 57.037914130033073 ], [ 25.01070112525673, 57.049437975282331 ], [ 24.9893070820562, 57.060600084426312 ], [ 24.984036086296328, 57.079927070078213 ], [ 24.93567361514522, 57.080917644533656 ], [ 24.89951592621901, 57.087871046111843 ], [ 24.878655721484336, 57.104559210439163 ], [ 24.86613959846369, 57.093433767554302 ], [ 24.888390483334149, 57.055885398492308 ], [ 24.88004640162012, 57.037806554029203 ], [ 24.889781164369197, 57.011383627852126 ], [ 24.845279392829696, 56.984960700775673 ], [ 24.817845460499598, 56.989510542777282 ], [ 24.774937320900619, 56.998449738264753 ], [ 24.726665663289737, 56.989510542777282 ], [ 24.669454810191326, 57.012752451764186 ], [ 24.630122348067914, 57.061024109375126 ], [ 24.650584145042217, 57.073964242955412 ], [ 24.678397752254455, 57.060057439798982 ], [ 24.707602039602421, 57.062838800070551 ], [ 24.759057211820846, 57.078136284262087 ], [ 24.749322449971089, 57.098996488996761 ], [ 24.766010613399089, 57.115684653324081 ], [ 24.77991741745484, 57.135154177922971 ], [ 24.79660558088284, 57.142107579501157 ], [ 24.804949663496132, 57.165749145406721 ], [ 24.823028507959236, 57.182437309734041 ], [ 24.861967557157016, 57.189390711312228 ], [ 24.884218442926795, 57.199125473162042 ], [ 24.900371942311267, 57.218678290270873 ], [ 24.934375033680851, 57.20839468027026 ], [ 24.967396274918144, 57.185011094786148 ], [ 24.973132358671421, 57.177750555843431 ], [ 24.975664504213455, 57.170670884953324 ], [ 24.978248324800347, 57.158733629453423 ], [ 24.983570998302923, 57.145013536345573 ], [ 24.984036086296328, 57.140750230938352 ], [ 24.987446730082524, 57.124678860341078 ], [ 25.026374629485531, 57.119228071218458 ], [ 25.067082243150992, 57.114531038976224 ], [ 25.112486889958006, 57.10983400673399 ], [ 25.137537729482631, 57.0894801994516 ], [ 25.168851279562887, 57.067560714755132 ], [ 25.13284069724034, 57.051903940614352 ], [ 25.150063149694574, 57.03311581074604 ], [ 25.168851279562887, 57.014327680877727 ], [ 25.167285602148809, 56.998670905837628 ], [ 25.147540316632274, 56.989389959878736 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-059", "NAME_1": "Madona" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.953324130104477, 56.846677123550364 ], [ 26.94401980986629, 56.840251776897958 ], [ 26.928465204105862, 56.831105048459165 ], [ 26.907536247999417, 56.817979234554002 ], [ 26.782944369586971, 56.707391669190145 ], [ 26.772557406798853, 56.681010851069914 ], [ 26.74392281748402, 56.696495154128058 ], [ 26.711043590889005, 56.707454896026604 ], [ 26.707912236060849, 56.723111671066761 ], [ 26.682861396536225, 56.721545993652683 ], [ 26.648416491627756, 56.716848960511072 ], [ 26.626497007830608, 56.704323541198448 ], [ 26.607708877962352, 56.69023244357237 ], [ 26.57639532788204, 56.691798120986448 ], [ 26.535687714216635, 56.677707024259746 ], [ 26.388514030997726, 56.677707024259746 ], [ 26.375579054130014, 56.635923163123437 ], [ 26.350774367143288, 56.60998159457489 ], [ 26.342712844322307, 56.60383209877233 ], [ 26.291346470263363, 56.58670136187834 ], [ 26.284215121630552, 56.605795803533454 ], [ 26.281269565837761, 56.610911770561756 ], [ 26.261994256129981, 56.616570339949192 ], [ 26.224632196019002, 56.609878241787385 ], [ 26.198225538577731, 56.6271381698906 ], [ 26.204891799217137, 56.635251370454341 ], [ 26.19109419084424, 56.642331041344448 ], [ 26.170010206905488, 56.645018216517428 ], [ 26.156470981850305, 56.650134181747035 ], [ 26.131046177239284, 56.653906562537827 ], [ 26.09223717630465, 56.643881334056289 ], [ 26.058130731248241, 56.629954536272805 ], [ 26.039992303102281, 56.626466376322185 ], [ 26.022887403730692, 56.629308580226791 ], [ 26.002113478154456, 56.639695543014852 ], [ 25.983613315701916, 56.665637112462719 ], [ 25.93209191291146, 56.637835191041233 ], [ 25.929818149787764, 56.64984996180624 ], [ 25.925115593909538, 56.654268296844407 ], [ 25.916278923833261, 56.659022529566073 ], [ 25.913798456033874, 56.664810289263414 ], [ 25.91565880710823, 56.668866889095625 ], [ 25.926975945883157, 56.671476549003501 ], [ 25.933332147260785, 56.675920722463331 ], [ 25.93570926317193, 56.681553453429103 ], [ 25.906822137031952, 56.697263088820478 ], [ 25.83437177903437, 56.712275092221716 ], [ 25.825535108958093, 56.740774644734017 ], [ 25.826930372938364, 56.74932709397018 ], [ 25.837317335726425, 56.756587632912897 ], [ 25.843828565835622, 56.764442451058244 ], [ 25.850804884837544, 56.77033356444241 ], [ 25.842743361117186, 56.789247138044971 ], [ 25.827705519294284, 56.800951850447518 ], [ 25.81199588390291, 56.801804511169223 ], [ 25.806259800149633, 56.804750067861335 ], [ 25.797113070811577, 56.811829738751442 ], [ 25.785795932935912, 56.818702704066538 ], [ 25.765487095353137, 56.82818533018883 ], [ 25.757546009740565, 56.851497223518038 ], [ 25.748151944356721, 56.867153998558138 ], [ 25.768505751639111, 56.879679417870818 ], [ 25.774768461295423, 56.890639160668684 ], [ 25.807647688789814, 56.895336192910975 ], [ 25.835829883142594, 56.900033225153209 ], [ 25.85931504525314, 56.906295934809521 ], [ 25.890628594434077, 56.900033225153209 ], [ 25.915679433958701, 56.906295934809521 ], [ 25.946992984038957, 56.906295934809521 ], [ 25.953255693695269, 56.925084064677833 ], [ 25.943861629210801, 56.932912452647543 ], [ 25.920376467100255, 56.943872194546088 ], [ 25.886358269818288, 56.964223537686109 ], [ 25.917829217444421, 56.975230618098522 ], [ 25.920774774136476, 56.981896877838608 ], [ 25.923565301197698, 56.992955634195141 ], [ 25.919999626881236, 57.000086981928632 ], [ 25.911162956805015, 57.006753242568038 ], [ 25.904341668333302, 57.013962103768051 ], [ 25.895660027887971, 57.026261095373229 ], [ 25.896745232606406, 57.037552394827117 ], [ 25.899535759667572, 57.05016144479481 ], [ 25.906202020306978, 57.061426906726354 ], [ 25.907597284287249, 57.0700051952835 ], [ 25.923100213204236, 57.097445379700503 ], [ 25.949661900276453, 57.100390937291877 ], [ 26.022577345368177, 57.079513658028873 ], [ 26.038390334446376, 57.076852322176876 ], [ 26.048828973178558, 57.076774806911772 ], [ 26.052859734589049, 57.080262966862392 ], [ 26.056270379274565, 57.084422919482108 ], [ 26.064952019719897, 57.08871206331105 ], [ 26.077044304850745, 57.088221136895925 ], [ 26.089601677975054, 57.086128241824895 ], [ 26.116783481772245, 57.078066718104537 ], [ 26.183187696754771, 57.082614244351873 ], [ 26.186541637386654, 57.058166650270664 ], [ 26.175581894588731, 57.034681488160118 ], [ 26.177147572002809, 57.006499293807337 ], [ 26.186541637386654, 56.984579809110869 ], [ 26.172450539760575, 56.965791679242557 ], [ 26.170884862346497, 56.953266259030613 ], [ 26.18184460514442, 56.953266259030613 ], [ 26.200632734113356, 56.951700581616535 ], [ 26.222552218809824, 56.928215419505989 ], [ 26.242906026092214, 56.929781096920067 ], [ 26.267956865616839, 56.931346774334145 ], [ 26.286744995485151, 56.940740839717932 ], [ 26.325886932635797, 56.931346774334145 ], [ 26.358766159230868, 56.931346774334145 ], [ 26.352503449574499, 56.948569226788379 ], [ 26.374422934270967, 56.956397614758089 ], [ 26.39321106413928, 56.970488711484847 ], [ 26.405736483451903, 56.989276841353103 ], [ 26.383816998755492, 57.004933616393259 ], [ 26.375988611685045, 57.031550133331962 ], [ 26.369377883282652, 57.044632065717224 ], [ 26.46203372607556, 57.061271877994727 ], [ 26.497277052693789, 57.055794175760582 ], [ 26.512624952879264, 57.044063625835634 ], [ 26.532933791361359, 57.025253404121258 ], [ 26.544250929236966, 57.009233710367425 ], [ 26.559650506265825, 56.978899645202432 ], [ 26.568177117979587, 56.94841055040655 ], [ 26.473060614955386, 56.854628578346194 ], [ 26.504374164136323, 56.832709093649726 ], [ 26.516899584348323, 56.824880706579336 ], [ 26.512202552106089, 56.812355286367335 ], [ 26.535687714216635, 56.801395544468789 ], [ 26.56073855374126, 56.809223931539179 ], [ 26.577961005296117, 56.804526899296945 ], [ 26.60927455537643, 56.818617996922967 ], [ 26.620234297274976, 56.832709093649726 ], [ 26.6734673320517, 56.867153998558138 ], [ 26.679730041708012, 56.884376450113052 ], [ 26.712609268303083, 56.903164579981365 ], [ 26.726700365929105, 56.890639160668684 ], [ 26.8002872070889, 56.895336192910975 ], [ 26.837863465926205, 56.900033225153209 ], [ 26.861348628036751, 56.900033225153209 ], [ 26.883268112733219, 56.88594212842645 ], [ 26.884833790147297, 56.873416708214506 ], [ 26.909884630571241, 56.873416708214506 ], [ 26.933369792681788, 56.86558832114406 ], [ 26.953324130104477, 56.846677123550364 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-057", "NAME_1": "Lubanas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.985102573025301, 56.878233953733968 ], [ 26.983707309944407, 56.870792548537281 ], [ 26.980761753252295, 56.865624905564914 ], [ 26.953324130104477, 56.846677123550364 ], [ 26.933369792681788, 56.86558832114406 ], [ 26.909884630571241, 56.873416708214506 ], [ 26.884833790147297, 56.873416708214506 ], [ 26.883268112733219, 56.88594212842645 ], [ 26.861348628036751, 56.900033225153209 ], [ 26.837863465926205, 56.900033225153209 ], [ 26.8002872070889, 56.895336192910975 ], [ 26.726700365929105, 56.890639160668684 ], [ 26.712609268303083, 56.903164579981365 ], [ 26.679730041708012, 56.884376450113052 ], [ 26.6734673320517, 56.867153998558138 ], [ 26.620234297274976, 56.832709093649726 ], [ 26.60927455537643, 56.818617996922967 ], [ 26.577961005296117, 56.804526899296945 ], [ 26.56073855374126, 56.809223931539179 ], [ 26.535687714216635, 56.801395544468789 ], [ 26.512202552106089, 56.812355286367335 ], [ 26.516899584348323, 56.824880706579336 ], [ 26.504374164136323, 56.832709093649726 ], [ 26.473060614955386, 56.854628578346194 ], [ 26.568177117979587, 56.94841055040655 ], [ 26.603678827016211, 56.953604030901261 ], [ 26.635201449687145, 56.96499868404203 ], [ 26.691063674049303, 56.978202013212297 ], [ 26.697419875426874, 56.973473618912351 ], [ 26.703776075905182, 56.974197089324207 ], [ 26.725996942305017, 56.982620348250464 ], [ 26.734833612381294, 56.988201402372795 ], [ 26.757622917763399, 56.992955634195141 ], [ 26.78154910740534, 56.99254222304512 ], [ 26.795811801973059, 56.989131578359604 ], [ 26.860045606619451, 56.983007920978764 ], [ 26.869812452682538, 56.976264146872836 ], [ 26.878184034765354, 56.978357041943923 ], [ 26.90257531060206, 56.977969469215623 ], [ 26.911877068671743, 56.973835354118251 ], [ 26.917148065330935, 56.969339504714355 ], [ 26.914822625363854, 56.957867337207801 ], [ 26.911101922315822, 56.947712918416414 ], [ 26.911411980678338, 56.939315497012558 ], [ 26.92396935380259, 56.931719062184925 ], [ 26.953941684661004, 56.920711981772513 ], [ 26.962468296374766, 56.913270576575826 ], [ 26.971149936820098, 56.890920518966766 ], [ 26.985102573025301, 56.878233953733968 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-102", "NAME_1": "Varaklanu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.772557406798853, 56.681010851069914 ], [ 26.771317173348848, 56.677471015175172 ], [ 26.77751834509553, 56.663725084544978 ], [ 26.778138461820561, 56.655921943243072 ], [ 26.780928988881726, 56.644966538774725 ], [ 26.777208285833694, 56.632615872124745 ], [ 26.779688755431721, 56.621453762081444 ], [ 26.788525424608622, 56.615485134331436 ], [ 26.792401157287543, 56.610291652937406 ], [ 26.788835483870457, 56.607061876304499 ], [ 26.749716423673988, 56.595822251895356 ], [ 26.736538933824704, 56.587295641080971 ], [ 26.729252557359644, 56.578303941373747 ], [ 26.743670281558252, 56.558847765411997 ], [ 26.722276239257042, 56.543112290699639 ], [ 26.706411574234721, 56.546083685813414 ], [ 26.627760043591138, 56.543732408323933 ], [ 26.608536410726742, 56.544714260254864 ], [ 26.595927361658369, 56.547039700221944 ], [ 26.586625603588743, 56.551432196838391 ], [ 26.583369989433436, 56.563524481969239 ], [ 26.593756952221554, 56.595150458326941 ], [ 26.522546827673921, 56.599853014205223 ], [ 26.484512973994526, 56.60613170031769 ], [ 26.401365593946991, 56.633856106474127 ], [ 26.375579054130014, 56.635923163123437 ], [ 26.388514030997726, 56.677707024259746 ], [ 26.535687714216635, 56.677707024259746 ], [ 26.57639532788204, 56.691798120986448 ], [ 26.607708877962352, 56.69023244357237 ], [ 26.626497007830608, 56.704323541198448 ], [ 26.648416491627756, 56.716848960511072 ], [ 26.682861396536225, 56.721545993652683 ], [ 26.707912236060849, 56.723111671066761 ], [ 26.711043590889005, 56.707454896026604 ], [ 26.74392281748402, 56.696495154128058 ], [ 26.772557406798853, 56.681010851069914 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-008", "NAME_1": "Amatas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.503922960975842, 56.994818797449511 ], [ 25.488191358744189, 56.99445425096286 ], [ 25.47263675298376, 56.989028225572099 ], [ 25.468761021204159, 56.988356432003684 ], [ 25.446901890010224, 56.99158620953591 ], [ 25.447366978003629, 56.981922716260328 ], [ 25.450157505064794, 56.977426865957057 ], [ 25.452637973763444, 56.96962372555447 ], [ 25.426696405214898, 56.948668931925624 ], [ 25.395845575213116, 56.937300117206632 ], [ 25.37222944483301, 56.941434231404628 ], [ 25.371609328107979, 56.94804881520065 ], [ 25.370265740971149, 56.952983914176286 ], [ 25.367165154648148, 56.959572659550588 ], [ 25.361687453313323, 56.966833198493305 ], [ 25.354556104680455, 56.97362864854324 ], [ 25.343704053898932, 56.977426865957057 ], [ 25.327425977726648, 56.977245998803767 ], [ 25.284121128287438, 56.964817816888683 ], [ 25.274819370217756, 56.958539129876897 ], [ 25.261796909100042, 56.952854722967061 ], [ 25.256991001333631, 56.947221992001289 ], [ 25.247327508058049, 56.942674465753953 ], [ 25.246707391333018, 56.939625556274393 ], [ 25.248412712776428, 56.937145087575686 ], [ 25.251668328730375, 56.934871324452047 ], [ 25.23399498857782, 56.934302884570457 ], [ 25.142579380134293, 56.950787665418375 ], [ 25.138083529831022, 56.980062364286653 ], [ 25.147540316632274, 56.989389959878736 ], [ 25.167285602148809, 56.998670905837628 ], [ 25.168851279562887, 57.014327680877727 ], [ 25.150063149694574, 57.03311581074604 ], [ 25.13284069724034, 57.051903940614352 ], [ 25.168851279562887, 57.067560714755132 ], [ 25.137537729482631, 57.0894801994516 ], [ 25.112486889958006, 57.10983400673399 ], [ 25.137537729482631, 57.111399684148068 ], [ 25.173548311805121, 57.11766239380438 ], [ 25.195467796501589, 57.133319168844537 ], [ 25.212690248955823, 57.145844589056537 ], [ 25.189205086845277, 57.163067040611395 ], [ 25.178245344047355, 57.181855170479707 ], [ 25.171982634391043, 57.206906010004332 ], [ 25.159457214179099, 57.227259817286722 ], [ 25.139103407796028, 57.233522526943034 ], [ 25.10465850288756, 57.233522526943034 ], [ 25.090567405261538, 57.247613624569112 ], [ 25.131275019826262, 57.266401754437368 ], [ 25.165719924734731, 57.263270399609212 ], [ 25.206427538400135, 57.26483607702329 ], [ 25.231478377924759, 57.28518988340636 ], [ 25.231478377924759, 57.296149626204283 ], [ 25.287842767529696, 57.280492851164126 ], [ 25.319156316710632, 57.258573366467658 ], [ 25.317590639296554, 57.231956849528956 ], [ 25.359863931275413, 57.231956849528956 ], [ 25.369257995759881, 57.219431430216275 ], [ 25.364560963517647, 57.205340332590254 ], [ 25.380217738557747, 57.188117880136019 ], [ 25.41153128773874, 57.186552202721941 ], [ 25.444410514333754, 57.170895428581161 ], [ 25.458501611959832, 57.156804330955083 ], [ 25.4960778716964, 57.152107298712849 ], [ 25.508603291009081, 57.156804330955083 ], [ 25.53208845311957, 57.156804330955083 ], [ 25.563402002300563, 57.147410266470615 ], [ 25.541482518503415, 57.122359426945991 ], [ 25.564967680613961, 57.112965361562146 ], [ 25.594715552380819, 57.10983400673399 ], [ 25.618200714491365, 57.080086134967132 ], [ 25.616635037077288, 57.065995037341054 ], [ 25.58688716441111, 57.050338262300954 ], [ 25.547745228159727, 57.020590390534096 ], [ 25.528957098291414, 57.020590390534096 ], [ 25.507037613594946, 57.036247165574196 ], [ 25.492946516868244, 57.026853101089728 ], [ 25.510168968423159, 57.008064971221415 ], [ 25.50399548410428, 56.994820478282406 ], [ 25.503922960975842, 56.994818797449511 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-030", "NAME_1": "Erglu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.488191358744189, 56.99445425096286 ], [ 25.503922960975842, 56.994818797449511 ], [ 25.50399548410428, 56.994820478282406 ], [ 25.517181837671671, 56.995126044531276 ], [ 25.565137567045042, 56.982103583413618 ], [ 25.582345819204193, 56.981896877838608 ], [ 25.602706332731032, 56.984377346537258 ], [ 25.634022250726275, 56.994712633381312 ], [ 25.638518101029547, 56.994764309325376 ], [ 25.638518101029547, 56.992955634195141 ], [ 25.636967808317706, 56.99021678307804 ], [ 25.639138217754521, 56.984480699324763 ], [ 25.63355716273287, 56.974868882892622 ], [ 25.629526401322323, 56.963086656124233 ], [ 25.69732588118444, 56.986134344824109 ], [ 25.705439080848862, 56.99367910370762 ], [ 25.727143182411908, 56.998691717948418 ], [ 25.754893426090746, 56.99329153097932 ], [ 25.769517856763684, 56.983498847393889 ], [ 25.779439731558341, 56.981018377795863 ], [ 25.787501255278698, 56.981690172263598 ], [ 25.796027866093141, 56.984997464161609 ], [ 25.813701206245639, 56.986754462448459 ], [ 25.822589553165301, 56.984997464161609 ], [ 25.829720899999529, 56.982413641776134 ], [ 25.833906691040966, 56.979726468401793 ], [ 25.837472365357371, 56.976780910810362 ], [ 25.841813185130377, 56.973835354118251 ], [ 25.844913770554058, 56.970269679801845 ], [ 25.850339796844139, 56.9663681104999 ], [ 25.854060499892114, 56.962569892186764 ], [ 25.886358269818288, 56.964223537686109 ], [ 25.920376467100255, 56.943872194546088 ], [ 25.943861629210801, 56.932912452647543 ], [ 25.953255693695269, 56.925084064677833 ], [ 25.946992984038957, 56.906295934809521 ], [ 25.915679433958701, 56.906295934809521 ], [ 25.890628594434077, 56.900033225153209 ], [ 25.85931504525314, 56.906295934809521 ], [ 25.835829883142594, 56.900033225153209 ], [ 25.807647688789814, 56.895336192910975 ], [ 25.774768461295423, 56.890639160668684 ], [ 25.768505751639111, 56.879679417870818 ], [ 25.748151944356721, 56.867153998558138 ], [ 25.757546009740565, 56.851497223518038 ], [ 25.765487095353137, 56.82818533018883 ], [ 25.746366815276303, 56.825937405486854 ], [ 25.73970055463684, 56.818366808181622 ], [ 25.748382195981549, 56.814671943555311 ], [ 25.748847283974953, 56.808832506115209 ], [ 25.746366815276303, 56.801132718500128 ], [ 25.734274530145456, 56.789350490832476 ], [ 25.730243767835589, 56.782322495886433 ], [ 25.692209914156194, 56.77048859317398 ], [ 25.668800490250419, 56.788988756525839 ], [ 25.641618687352548, 56.792115180371241 ], [ 25.605186802329058, 56.802476304737638 ], [ 25.561726922359583, 56.802579658424463 ], [ 25.505399610903282, 56.81309581242175 ], [ 25.506484815621718, 56.826712550943455 ], [ 25.504779494178308, 56.833068753220346 ], [ 25.501058791130276, 56.841957099240688 ], [ 25.492067092322429, 56.846246243069629 ], [ 25.483953891758688, 56.853842677897262 ], [ 25.484884066846178, 56.857046617007768 ], [ 25.483643833396172, 56.867175198276755 ], [ 25.55568078024379, 56.873944810804346 ], [ 25.53408003056893, 56.884951891216758 ], [ 25.527723830090679, 56.890067857345684 ], [ 25.520747511988077, 56.894201972443057 ], [ 25.51594160332229, 56.897922675491031 ], [ 25.516251661684805, 56.903193671250904 ], [ 25.519197219276236, 56.907896227129129 ], [ 25.524003127042647, 56.913632310882406 ], [ 25.532219678595254, 56.921409613762648 ], [ 25.53159956187028, 56.932649238171791 ], [ 25.522762891794002, 56.943501288053994 ], [ 25.516871779309156, 56.956317044495961 ], [ 25.508345167595394, 56.964481920104504 ], [ 25.507725050870363, 56.972750149399872 ], [ 25.49268720904746, 56.981276760214257 ], [ 25.488191358744189, 56.99445425096286 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-021", "NAME_1": "Cesvaines" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.321318801121777, 57.097910467693907 ], [ 26.328915235949353, 57.071865546357856 ], [ 26.369377883282652, 57.044632065717224 ], [ 26.375988611685045, 57.031550133331962 ], [ 26.383816998755492, 57.004933616393259 ], [ 26.405736483451903, 56.989276841353103 ], [ 26.39321106413928, 56.970488711484847 ], [ 26.374422934270967, 56.956397614758089 ], [ 26.352503449574499, 56.948569226788379 ], [ 26.358766159230868, 56.931346774334145 ], [ 26.325886932635797, 56.931346774334145 ], [ 26.286744995485151, 56.940740839717932 ], [ 26.267956865616839, 56.931346774334145 ], [ 26.242906026092214, 56.929781096920067 ], [ 26.222552218809824, 56.928215419505989 ], [ 26.200632734113356, 56.951700581616535 ], [ 26.18184460514442, 56.953266259030613 ], [ 26.170884862346497, 56.953266259030613 ], [ 26.172450539760575, 56.965791679242557 ], [ 26.186541637386654, 56.984579809110869 ], [ 26.177147572002809, 57.006499293807337 ], [ 26.175581894588731, 57.034681488160118 ], [ 26.186541637386654, 57.058166650270664 ], [ 26.183187696754771, 57.082614244351873 ], [ 26.21517540921775, 57.08912547536039 ], [ 26.27165774940562, 57.087110093755769 ], [ 26.321318801121777, 57.097910467693907 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-067", "NAME_1": "Ogre" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.468761021204159, 56.988356432003684 ], [ 25.47263675298376, 56.989028225572099 ], [ 25.488191358744189, 56.99445425096286 ], [ 25.49268720904746, 56.981276760214257 ], [ 25.507725050870363, 56.972750149399872 ], [ 25.508345167595394, 56.964481920104504 ], [ 25.516871779309156, 56.956317044495961 ], [ 25.522762891794002, 56.943501288053994 ], [ 25.53159956187028, 56.932649238171791 ], [ 25.532219678595254, 56.921409613762648 ], [ 25.524003127042647, 56.913632310882406 ], [ 25.519197219276236, 56.907896227129129 ], [ 25.516251661684805, 56.903193671250904 ], [ 25.51594160332229, 56.897922675491031 ], [ 25.520747511988077, 56.894201972443057 ], [ 25.527723830090679, 56.890067857345684 ], [ 25.53408003056893, 56.884951891216758 ], [ 25.55568078024379, 56.873944810804346 ], [ 25.483643833396172, 56.867175198276755 ], [ 25.484884066846178, 56.857046617007768 ], [ 25.483953891758688, 56.853842677897262 ], [ 25.492067092322429, 56.846246243069629 ], [ 25.501058791130276, 56.841957099240688 ], [ 25.504779494178308, 56.833068753220346 ], [ 25.506484815621718, 56.826712550943455 ], [ 25.505399610903282, 56.81309581242175 ], [ 25.437445103208915, 56.775191148152942 ], [ 25.391194696178275, 56.761264350369458 ], [ 25.378017206328991, 56.760049954441797 ], [ 25.365098097099462, 56.762840481503019 ], [ 25.353470899962019, 56.762530423140504 ], [ 25.353935987955481, 56.759326484029998 ], [ 25.356106398291615, 56.753383693802391 ], [ 25.326030714645754, 56.757982896893168 ], [ 25.29528323743142, 56.754520576263531 ], [ 25.26815310957835, 56.745218818193905 ], [ 25.257094354121136, 56.736123765699176 ], [ 25.293112827095285, 56.723359686999913 ], [ 25.298073765391962, 56.719328924690103 ], [ 25.296523471780802, 56.716280016109806 ], [ 25.295593295793935, 56.713024400155916 ], [ 25.278385043634842, 56.696849676771137 ], [ 25.217716913304912, 56.708218492389506 ], [ 25.215546502069401, 56.70522125885401 ], [ 25.200456984302377, 56.697805691179667 ], [ 25.176298666150501, 56.703333667314894 ], [ 25.153483106859881, 56.708554389173685 ], [ 25.148987258355248, 56.714523016923692 ], [ 25.097465854665415, 56.737157294473548 ], [ 25.074967533025188, 56.753948557080207 ], [ 25.059384292850837, 56.769531797254615 ], [ 25.041574875380149, 56.789567392021524 ], [ 25.005956041338209, 56.79401974571465 ], [ 24.968111029999989, 56.798472100307151 ], [ 24.943623081539954, 56.809602985889057 ], [ 24.923587487672421, 56.800698277603374 ], [ 24.885742476334201, 56.814055340481559 ], [ 24.843445110403479, 56.827412403359688 ], [ 24.816730984647222, 56.834090934349092 ], [ 24.805600099065316, 56.807376808592835 ], [ 24.778885973309002, 56.800698277603374 ], [ 24.783338327002184, 56.780662682836521 ], [ 24.761076555838372, 56.758400911672709 ], [ 24.724864128776517, 56.736436264821748 ], [ 24.704112175436535, 56.753037828033314 ], [ 24.697520378662546, 56.756903387581247 ], [ 24.678635353139043, 56.760212482619977 ], [ 24.661062045477024, 56.76329173342674 ], [ 24.652842643972804, 56.767523505005727 ], [ 24.645762565689836, 56.773138738761759 ], [ 24.625417514206902, 56.783148504300016 ], [ 24.618418815575865, 56.784857488682121 ], [ 24.596202019507587, 56.806423243937502 ], [ 24.587657097596946, 56.812201238796092 ], [ 24.616611454355109, 56.824632180730532 ], [ 24.666670950703804, 56.853237607729397 ], [ 24.700639894815254, 56.838934894229965 ], [ 24.736396678563835, 56.865752481591699 ], [ 24.702427734452385, 56.874691677079227 ], [ 24.693273497573841, 56.895471531885676 ], [ 24.715887079025435, 56.897147529135111 ], [ 24.738056267682566, 56.909394842997585 ], [ 24.760277134082457, 56.914975898019236 ], [ 24.807406040256524, 56.933889472521116 ], [ 24.840323927806992, 56.926034654375769 ], [ 24.877272576768007, 56.921487128128433 ], [ 24.890450066617291, 56.922675686533694 ], [ 24.918820427920366, 56.934638780455316 ], [ 24.938250767259092, 56.939108792336867 ], [ 24.954838900894515, 56.940607408205324 ], [ 24.976594679301002, 56.935413926811236 ], [ 25.000159132837666, 56.939573880330272 ], [ 25.036332635442761, 56.933992825308621 ], [ 25.142579380134293, 56.950787665418375 ], [ 25.23399498857782, 56.934302884570457 ], [ 25.251668328730375, 56.934871324452047 ], [ 25.248412712776428, 56.937145087575686 ], [ 25.246707391333018, 56.939625556274393 ], [ 25.247327508058049, 56.942674465753953 ], [ 25.256991001333631, 56.947221992001289 ], [ 25.261796909100042, 56.952854722967061 ], [ 25.274819370217756, 56.958539129876897 ], [ 25.284121128287438, 56.964817816888683 ], [ 25.327425977726648, 56.977245998803767 ], [ 25.343704053898932, 56.977426865957057 ], [ 25.354556104680455, 56.97362864854324 ], [ 25.361687453313323, 56.966833198493305 ], [ 25.367165154648148, 56.959572659550588 ], [ 25.370265740971149, 56.952983914176286 ], [ 25.371609328107979, 56.94804881520065 ], [ 25.37222944483301, 56.941434231404628 ], [ 25.395845575213116, 56.937300117206632 ], [ 25.426696405214898, 56.948668931925624 ], [ 25.452637973763444, 56.96962372555447 ], [ 25.450157505064794, 56.977426865957057 ], [ 25.447366978003629, 56.981922716260328 ], [ 25.446901890010224, 56.99158620953591 ], [ 25.468761021204159, 56.988356432003684 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-080", "NAME_1": "Ropazu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.807406040256524, 56.933889472521116 ], [ 24.760277134082457, 56.914975898019236 ], [ 24.738056267682566, 56.909394842997585 ], [ 24.715887079025435, 56.897147529135111 ], [ 24.693273497573841, 56.895471531885676 ], [ 24.574345330872291, 56.886657213559545 ], [ 24.520188428852919, 56.900067247405502 ], [ 24.507010939003635, 56.896475735566696 ], [ 24.511351759675961, 56.877872219427331 ], [ 24.507786086258875, 56.873014634817537 ], [ 24.499879592169407, 56.87035329896554 ], [ 24.491818068449106, 56.871619370837266 ], [ 24.485616895803105, 56.875365912306961 ], [ 24.479725783318202, 56.880481879335207 ], [ 24.469287143686699, 56.884176743961518 ], [ 24.457349888186798, 56.886838079813515 ], [ 24.428888342822745, 56.924751173427865 ], [ 24.405646432936521, 56.926539013064996 ], [ 24.371677488825014, 56.944417404939259 ], [ 24.395915417662366, 56.968056472528133 ], [ 24.401278935674327, 56.994874059889924 ], [ 24.451338432023022, 57.00202541663964 ], [ 24.53179119410828, 57.00202541663964 ], [ 24.521064159883053, 57.052084912988335 ], [ 24.546093907607712, 57.061024109375126 ], [ 24.565530100633623, 57.060001151232143 ], [ 24.580062851719219, 57.059236269738051 ], [ 24.630122348067914, 57.061024109375126 ], [ 24.669454810191326, 57.012752451764186 ], [ 24.726665663289737, 56.989510542777282 ], [ 24.774937320900619, 56.998449738264753 ], [ 24.817845460499598, 56.989510542777282 ], [ 24.816057621761843, 56.966268633790378 ], [ 24.807406040256524, 56.933889472521116 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-035", "NAME_1": "Ikskiles" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.512695346812791, 56.817100735410577 ], [ 24.505305616660849, 56.818521836913192 ], [ 24.475850050639281, 56.832216092498641 ], [ 24.457763299336762, 56.837538764202577 ], [ 24.452182245214431, 56.837926336930877 ], [ 24.444017367807248, 56.837667955411803 ], [ 24.442932163088813, 56.845290229560419 ], [ 24.447583042123654, 56.857563381844557 ], [ 24.448203158848685, 56.86554739029981 ], [ 24.444482455800653, 56.872368678771466 ], [ 24.437816196060567, 56.878233953733968 ], [ 24.435645785724432, 56.884021715229949 ], [ 24.439831576765812, 56.887484035859529 ], [ 24.457349888186798, 56.886838079813515 ], [ 24.469287143686699, 56.884176743961518 ], [ 24.479725783318202, 56.880481879335207 ], [ 24.485616895803105, 56.875365912306961 ], [ 24.491818068449106, 56.871619370837266 ], [ 24.499879592169407, 56.87035329896554 ], [ 24.507786086258875, 56.873014634817537 ], [ 24.511351759675961, 56.877872219427331 ], [ 24.507010939003635, 56.896475735566696 ], [ 24.520188428852919, 56.900067247405502 ], [ 24.574345330872291, 56.886657213559545 ], [ 24.693273497573841, 56.895471531885676 ], [ 24.702427734452385, 56.874691677079227 ], [ 24.736396678563835, 56.865752481591699 ], [ 24.700639894815254, 56.838934894229965 ], [ 24.666670950703804, 56.853237607729397 ], [ 24.616611454355109, 56.824632180730532 ], [ 24.587657097596946, 56.812201238796092 ], [ 24.565928581239007, 56.816433009475759 ], [ 24.525563998679559, 56.814276434309988 ], [ 24.512695346812791, 56.817100735410577 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-013", "NAME_1": "Baldones" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.525459426411373, 56.75240184187146 ], [ 24.521893752094968, 56.713644517780267 ], [ 24.482361280748478, 56.704704494916484 ], [ 24.443397251082274, 56.684240627702764 ], [ 24.42789432126591, 56.677677720750182 ], [ 24.414975212935701, 56.674267076064666 ], [ 24.383659294940514, 56.670856432278526 ], [ 24.376062860112881, 56.66840180110222 ], [ 24.372962273789881, 56.666308906031134 ], [ 24.374047479407636, 56.659590969447663 ], [ 24.355358623901338, 56.661563541524174 ], [ 24.335858595197976, 56.663621730858154 ], [ 24.294569125564692, 56.675455634469927 ], [ 24.274725375975322, 56.683956406862649 ], [ 24.263408237200395, 56.690441800348765 ], [ 24.257827183078007, 56.701190497443463 ], [ 24.245683221103775, 56.70367096614217 ], [ 24.230455055756181, 56.699422996873807 ], [ 24.20540844664248, 56.713786152908597 ], [ 24.188165019300186, 56.717943050531346 ], [ 24.194681412417253, 56.740603740270387 ], [ 24.228650356528703, 56.751330775394933 ], [ 24.253680104253363, 56.738815900633256 ], [ 24.269770656490607, 56.745967257382972 ], [ 24.26261930064021, 56.762057809620217 ], [ 24.253680104253363, 56.78529971950644 ], [ 24.294800405114586, 56.78887539788127 ], [ 24.346647741100355, 56.787087558244195 ], [ 24.414585629323312, 56.776360523119592 ], [ 24.466432964409762, 56.763845649257291 ], [ 24.525459426411373, 56.75240184187146 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-051", "NAME_1": "Keguma" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.521893752094968, 56.713644517780267 ], [ 24.525459426411373, 56.75240184187146 ], [ 24.53310753808239, 56.759223131242493 ], [ 24.537603386587023, 56.770385240386531 ], [ 24.536983269861992, 56.777490749698302 ], [ 24.522978956813404, 56.795293281060026 ], [ 24.512695346812791, 56.817100735410577 ], [ 24.525563998679559, 56.814276434309988 ], [ 24.565928581239007, 56.816433009475759 ], [ 24.587657097596946, 56.812201238796092 ], [ 24.596202019507587, 56.806423243937502 ], [ 24.618418815575865, 56.784857488682121 ], [ 24.625417514206902, 56.783148504300016 ], [ 24.645762565689836, 56.773138738761759 ], [ 24.652842643972804, 56.767523505005727 ], [ 24.661062045477024, 56.76329173342674 ], [ 24.678635353139043, 56.760212482619977 ], [ 24.697520378662546, 56.756903387581247 ], [ 24.704112175436535, 56.753037828033314 ], [ 24.724864128776517, 56.736436264821748 ], [ 24.761076555838372, 56.758400911672709 ], [ 24.783338327002184, 56.780662682836521 ], [ 24.778885973309002, 56.800698277603374 ], [ 24.805600099065316, 56.807376808592835 ], [ 24.816730984647222, 56.834090934349092 ], [ 24.843445110403479, 56.827412403359688 ], [ 24.885742476334201, 56.814055340481559 ], [ 24.923587487672421, 56.800698277603374 ], [ 24.903551892905512, 56.787341214725245 ], [ 24.872385413456072, 56.767305619958393 ], [ 24.852349818689163, 56.762853266265211 ], [ 24.845671287699759, 56.738365317805119 ], [ 24.818957161943445, 56.729460608620172 ], [ 24.799754982406512, 56.715212373352301 ], [ 24.817773611862663, 56.711897095474569 ], [ 24.834564649638878, 56.708807684127294 ], [ 24.852389025533057, 56.701708144515237 ], [ 24.868174675221042, 56.695420639650763 ], [ 24.878173388990717, 56.682521200393069 ], [ 24.905202670466849, 56.647650457502607 ], [ 24.919688347439262, 56.640204168987168 ], [ 24.938080274684921, 56.638210354024579 ], [ 24.957106048605226, 56.631887675775602 ], [ 24.975142934298958, 56.625893627788685 ], [ 24.991709831343599, 56.620388087939887 ], [ 25.024343703099419, 56.624554348404445 ], [ 25.026669142167179, 56.620058499000493 ], [ 25.019382764802742, 56.608121243500534 ], [ 25.049510125292045, 56.611996975280135 ], [ 25.056796501757105, 56.601325792551279 ], [ 25.042223747927608, 56.590034492198015 ], [ 24.968946566730665, 56.581559557327694 ], [ 24.949987336346453, 56.578568175680743 ], [ 24.732475212660916, 56.544249173160779 ], [ 24.657079298870542, 56.543577378693044 ], [ 24.664365676234979, 56.556832383807432 ], [ 24.671341994337524, 56.566780097023809 ], [ 24.681418897863807, 56.571327623271145 ], [ 24.697231886942006, 56.576236883825118 ], [ 24.697128534154501, 56.578872382154657 ], [ 24.691185743926951, 56.581662910115199 ], [ 24.673667433405285, 56.586546332247394 ], [ 24.666070997678389, 56.587166449871745 ], [ 24.661420118643548, 56.588329168955966 ], [ 24.660334913925112, 56.5904995792921 ], [ 24.662970412254708, 56.593961899921737 ], [ 24.662505324261303, 56.596855781569047 ], [ 24.656769239608707, 56.599801337361782 ], [ 24.629897495073294, 56.601454982861128 ], [ 24.617185093217472, 56.607191067513725 ], [ 24.599666781796543, 56.620678615725524 ], [ 24.59284549332483, 56.643028673334584 ], [ 24.58974490700183, 56.661037910271318 ], [ 24.634083286114731, 56.676850898450198 ], [ 24.600907017045188, 56.707029933984245 ], [ 24.556051873095441, 56.706978258040124 ], [ 24.521893752094968, 56.713644517780267 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-034", "NAME_1": "Iecavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.245683221103775, 56.70367096614217 ], [ 24.257827183078007, 56.701190497443463 ], [ 24.263408237200395, 56.690441800348765 ], [ 24.274725375975322, 56.683956406862649 ], [ 24.294569125564692, 56.675455634469927 ], [ 24.335858595197976, 56.663621730858154 ], [ 24.355358623901338, 56.661563541524174 ], [ 24.368101810450185, 56.619030677323849 ], [ 24.309103118614019, 56.611879320574133 ], [ 24.321617992476376, 56.56360766296325 ], [ 24.34843557983811, 56.565395501701005 ], [ 24.387768041961522, 56.552880627838704 ], [ 24.380616685211862, 56.529638718851743 ], [ 24.34485990146328, 56.536790075601459 ], [ 24.300163923126547, 56.535002235964384 ], [ 24.251892265515608, 56.518911683727197 ], [ 24.234013873641345, 56.501033291852934 ], [ 24.194681412417253, 56.509972488239725 ], [ 24.173227342168104, 56.517123844989442 ], [ 24.1499854331812, 56.524275201739158 ], [ 24.105289453945147, 56.529638718851743 ], [ 24.078871697970442, 56.535464179028622 ], [ 24.088380160715815, 56.550760403269976 ], [ 24.10527835541177, 56.5639637324403 ], [ 24.11023929190975, 56.574944973531672 ], [ 24.106673617593344, 56.580370998922433 ], [ 24.094271274999301, 56.581766262003384 ], [ 24.081558872244159, 56.581895454111873 ], [ 24.055617302796293, 56.58574534836913 ], [ 24.040889520235169, 56.592411607209897 ], [ 24.026575147924746, 56.602746894053894 ], [ 24.02647179513724, 56.607837023559739 ], [ 24.030037468554326, 56.608793037069006 ], [ 24.039339226624008, 56.608793037069006 ], [ 24.049106072687096, 56.610989284927541 ], [ 24.057477654769912, 56.619076647069562 ], [ 24.055152214802831, 56.623779202048524 ], [ 24.051896599748261, 56.627964993089904 ], [ 24.050346307036421, 56.631840724869505 ], [ 24.050966423761452, 56.635251370454341 ], [ 24.046160515994984, 56.637757677574712 ], [ 24.036393669931897, 56.641478379723424 ], [ 24.009987013389946, 56.658867499035807 ], [ 24.006731398335376, 56.664758613319293 ], [ 24.00890180777219, 56.666205553243685 ], [ 24.017738477848468, 56.662743231714728 ], [ 24.023474561601745, 56.658505763829908 ], [ 24.034533318857541, 56.659487617559478 ], [ 24.042129753685174, 56.661244614947009 ], [ 24.033913202132567, 56.68821971316919 ], [ 24.08713992726581, 56.682070218265949 ], [ 24.117990757267592, 56.671373196215995 ], [ 24.131013218385306, 56.668272609892995 ], [ 24.148583204850979, 56.668065904317984 ], [ 24.160210401988422, 56.67010712344495 ], [ 24.172457716750216, 56.678504543949543 ], [ 24.173697951099541, 56.685119126846189 ], [ 24.179434034852818, 56.690441800348765 ], [ 24.184705030612633, 56.692560532942196 ], [ 24.195867139756672, 56.691527005067201 ], [ 24.209096307348659, 56.693464871406661 ], [ 24.230455055756181, 56.699422996873807 ], [ 24.245683221103775, 56.70367096614217 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-072", "NAME_1": "Plavinu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.765487095353137, 56.82818533018883 ], [ 25.785795932935912, 56.818702704066538 ], [ 25.797113070811577, 56.811829738751442 ], [ 25.806259800149633, 56.804750067861335 ], [ 25.81199588390291, 56.801804511169223 ], [ 25.827705519294284, 56.800951850447518 ], [ 25.842743361117186, 56.789247138044971 ], [ 25.850804884837544, 56.77033356444241 ], [ 25.843828565835622, 56.764442451058244 ], [ 25.837317335726425, 56.756587632912897 ], [ 25.826930372938364, 56.74932709397018 ], [ 25.825535108958093, 56.740774644734017 ], [ 25.83437177903437, 56.712275092221716 ], [ 25.906822137031952, 56.697263088820478 ], [ 25.93570926317193, 56.681553453429103 ], [ 25.933332147260785, 56.675920722463331 ], [ 25.926975945883157, 56.671476549003501 ], [ 25.91565880710823, 56.668866889095625 ], [ 25.913798456033874, 56.664810289263414 ], [ 25.916278923833261, 56.659022529566073 ], [ 25.925115593909538, 56.654268296844407 ], [ 25.929818149787764, 56.64984996180624 ], [ 25.93209191291146, 56.637835191041233 ], [ 25.883257684394607, 56.623572496473514 ], [ 25.830961135248174, 56.613237210528837 ], [ 25.788276400735299, 56.609309801006475 ], [ 25.756908806795991, 56.612255356799267 ], [ 25.744041375309223, 56.617267971040008 ], [ 25.711330194233085, 56.605847480376895 ], [ 25.688334180577954, 56.600783190192033 ], [ 25.646734652582154, 56.596390693575643 ], [ 25.601466099281026, 56.578924058998098 ], [ 25.579400261612761, 56.576288561567821 ], [ 25.569788446079883, 56.576701971818522 ], [ 25.509043815819439, 56.578802801607992 ], [ 25.493581576728388, 56.589056707900738 ], [ 25.498789909526693, 56.602362371825961 ], [ 25.495534701864983, 56.611843166029246 ], [ 25.520202960797974, 56.61369939730929 ], [ 25.53133384637988, 56.642639700361826 ], [ 25.582535920596229, 56.676032357107545 ], [ 25.5691788577181, 56.711651192048862 ], [ 25.615928578241267, 56.716103545741987 ], [ 25.615928578241267, 56.738365317805119 ], [ 25.595892983474414, 56.751722380683304 ], [ 25.600345338066859, 56.765079443561433 ], [ 25.627059463823173, 56.762853266265211 ], [ 25.655999766875709, 56.753948557080207 ], [ 25.692209914156194, 56.77048859317398 ], [ 25.730243767835589, 56.782322495886433 ], [ 25.734274530145456, 56.789350490832476 ], [ 25.746366815276303, 56.801132718500128 ], [ 25.748847283974953, 56.808832506115209 ], [ 25.748382195981549, 56.814671943555311 ], [ 25.73970055463684, 56.818366808181622 ], [ 25.746366815276303, 56.825937405486854 ], [ 25.765487095353137, 56.82818533018883 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-046", "NAME_1": "Kokneses" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.505399610903282, 56.81309581242175 ], [ 25.561726922359583, 56.802579658424463 ], [ 25.605186802329058, 56.802476304737638 ], [ 25.641618687352548, 56.792115180371241 ], [ 25.668800490250419, 56.788988756525839 ], [ 25.692209914156194, 56.77048859317398 ], [ 25.655999766875709, 56.753948557080207 ], [ 25.627059463823173, 56.762853266265211 ], [ 25.600345338066859, 56.765079443561433 ], [ 25.595892983474414, 56.751722380683304 ], [ 25.615928578241267, 56.738365317805119 ], [ 25.615928578241267, 56.716103545741987 ], [ 25.5691788577181, 56.711651192048862 ], [ 25.582535920596229, 56.676032357107545 ], [ 25.53133384637988, 56.642639700361826 ], [ 25.520202960797974, 56.61369939730929 ], [ 25.495534701864983, 56.611843166029246 ], [ 25.487233919809512, 56.613470769860101 ], [ 25.464772982986744, 56.612901108699191 ], [ 25.423106316103599, 56.627305406019616 ], [ 25.398448112490371, 56.630723374783884 ], [ 25.366709831493495, 56.614365952326807 ], [ 25.330170117756722, 56.597601629811322 ], [ 25.293373786970676, 56.591109263881378 ], [ 25.293132892768767, 56.631508814779977 ], [ 25.293132892768767, 56.676032357107545 ], [ 25.278385043634842, 56.696849676771137 ], [ 25.295593295793935, 56.713024400155916 ], [ 25.296523471780802, 56.716280016109806 ], [ 25.298073765391962, 56.719328924690103 ], [ 25.293112827095285, 56.723359686999913 ], [ 25.257094354121136, 56.736123765699176 ], [ 25.26815310957835, 56.745218818193905 ], [ 25.29528323743142, 56.754520576263531 ], [ 25.326030714645754, 56.757982896893168 ], [ 25.356106398291615, 56.753383693802391 ], [ 25.353935987955481, 56.759326484029998 ], [ 25.353470899962019, 56.762530423140504 ], [ 25.365098097099462, 56.762840481503019 ], [ 25.378017206328991, 56.760049954441797 ], [ 25.391194696178275, 56.761264350369458 ], [ 25.437445103208915, 56.775191148152942 ], [ 25.505399610903282, 56.81309581242175 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-002", "NAME_1": "Aizkraukles" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.217716913304912, 56.708218492389506 ], [ 25.278385043634842, 56.696849676771137 ], [ 25.293132892768767, 56.676032357107545 ], [ 25.293132892768767, 56.631508814779977 ], [ 25.293373786970676, 56.591109263881378 ], [ 25.235362175723822, 56.585638739136471 ], [ 25.215342643747988, 56.5888939476975 ], [ 25.188987085785357, 56.601004367657424 ], [ 25.163096550165619, 56.612901108699191 ], [ 25.13860087922717, 56.619146840953931 ], [ 25.179597858754164, 56.640413523065604 ], [ 25.181824035151067, 56.660449117832513 ], [ 25.168466973172258, 56.680484711700046 ], [ 25.176298666150501, 56.703333667314894 ], [ 25.200456984302377, 56.697805691179667 ], [ 25.215546502069401, 56.70522125885401 ], [ 25.217716913304912, 56.708218492389506 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-092", "NAME_1": "Skriveru" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.097465854665415, 56.737157294473548 ], [ 25.148987258355248, 56.714523016923692 ], [ 25.153483106859881, 56.708554389173685 ], [ 25.176298666150501, 56.703333667314894 ], [ 25.168466973172258, 56.680484711700046 ], [ 25.181824035151067, 56.660449117832513 ], [ 25.179597858754164, 56.640413523065604 ], [ 25.13860087922717, 56.619146840953931 ], [ 25.118093295069514, 56.624375717865064 ], [ 25.080179878598528, 56.621497567158997 ], [ 25.049510125292045, 56.611996975280135 ], [ 25.019382764802742, 56.608121243500534 ], [ 25.026669142167179, 56.620058499000493 ], [ 25.024343703099419, 56.624554348404445 ], [ 25.021243116776418, 56.632124945709677 ], [ 25.01488691629811, 56.640935777364234 ], [ 25.017367384996817, 56.671683255477831 ], [ 25.016902297003412, 56.681501777485039 ], [ 25.019537795332951, 56.687547918701455 ], [ 25.024498731830988, 56.69054515223695 ], [ 25.032715285182235, 56.692818915360647 ], [ 25.041758660833523, 56.694162503396797 ], [ 25.055246209045265, 56.701371365496072 ], [ 25.053695916333481, 56.706099758896755 ], [ 25.046564568599933, 56.708218492389506 ], [ 25.041913689565092, 56.7130760769993 ], [ 25.036125928968431, 56.716796780047332 ], [ 25.036642693805277, 56.720775865513758 ], [ 25.028684522872425, 56.727132065992009 ], [ 25.097465854665415, 56.737157294473548 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-053", "NAME_1": "Lielvardes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.097465854665415, 56.737157294473548 ], [ 25.028684522872425, 56.727132065992009 ], [ 25.036642693805277, 56.720775865513758 ], [ 25.036125928968431, 56.716796780047332 ], [ 25.041913689565092, 56.7130760769993 ], [ 25.046564568599933, 56.708218492389506 ], [ 25.053695916333481, 56.706099758896755 ], [ 25.055246209045265, 56.701371365496072 ], [ 25.041758660833523, 56.694162503396797 ], [ 25.032715285182235, 56.692818915360647 ], [ 25.024498731830988, 56.69054515223695 ], [ 25.019537795332951, 56.687547918701455 ], [ 25.016902297003412, 56.681501777485039 ], [ 25.017367384996817, 56.671683255477831 ], [ 25.01488691629811, 56.640935777364234 ], [ 25.021243116776418, 56.632124945709677 ], [ 25.024343703099419, 56.624554348404445 ], [ 24.991709831343599, 56.620388087939887 ], [ 24.975142934298958, 56.625893627788685 ], [ 24.957106048605226, 56.631887675775602 ], [ 24.938080274684921, 56.638210354024579 ], [ 24.919688347439262, 56.640204168987168 ], [ 24.905202670466849, 56.647650457502607 ], [ 24.878173388990717, 56.682521200393069 ], [ 24.868174675221042, 56.695420639650763 ], [ 24.852389025533057, 56.701708144515237 ], [ 24.834564649638878, 56.708807684127294 ], [ 24.817773611862663, 56.711897095474569 ], [ 24.799754982406512, 56.715212373352301 ], [ 24.818957161943445, 56.729460608620172 ], [ 24.845671287699759, 56.738365317805119 ], [ 24.852349818689163, 56.762853266265211 ], [ 24.872385413456072, 56.767305619958393 ], [ 24.903551892905512, 56.787341214725245 ], [ 24.923587487672421, 56.800698277603374 ], [ 24.943623081539954, 56.809602985889057 ], [ 24.968111029999989, 56.798472100307151 ], [ 25.005956041338209, 56.79401974571465 ], [ 25.041574875380149, 56.789567392021524 ], [ 25.059384292850837, 56.769531797254615 ], [ 25.074967533025188, 56.753948557080207 ], [ 25.097465854665415, 56.737157294473548 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-038", "NAME_1": "Jaunjelgavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.949987336346453, 56.578568175680743 ], [ 24.968946566730665, 56.581559557327694 ], [ 25.042223747927608, 56.590034492198015 ], [ 25.056796501757105, 56.601325792551279 ], [ 25.049510125292045, 56.611996975280135 ], [ 25.080179878598528, 56.621497567158997 ], [ 25.118093295069514, 56.624375717865064 ], [ 25.13860087922717, 56.619146840953931 ], [ 25.163096550165619, 56.612901108699191 ], [ 25.188987085785357, 56.601004367657424 ], [ 25.215342643747988, 56.5888939476975 ], [ 25.235362175723822, 56.585638739136471 ], [ 25.293373786970676, 56.591109263881378 ], [ 25.330170117756722, 56.597601629811322 ], [ 25.366709831493495, 56.614365952326807 ], [ 25.398448112490371, 56.630723374783884 ], [ 25.423106316103599, 56.627305406019616 ], [ 25.464772982986744, 56.612901108699191 ], [ 25.487233919809512, 56.613470769860101 ], [ 25.495534701864983, 56.611843166029246 ], [ 25.498789909526693, 56.602362371825961 ], [ 25.493581576728388, 56.589056707900738 ], [ 25.509043815819439, 56.578802801607992 ], [ 25.569788446079883, 56.576701971818522 ], [ 25.560331659278631, 56.573859767913916 ], [ 25.580175408868001, 56.569157212935011 ], [ 25.599450717676461, 56.57029409449683 ], [ 25.607202183034303, 56.567710273010675 ], [ 25.605806919054032, 56.562025865201463 ], [ 25.598675571320541, 56.551819770466011 ], [ 25.575989617826565, 56.539701646913443 ], [ 25.558626336036582, 56.510659492041896 ], [ 25.566067743031908, 56.498308824492653 ], [ 25.580020379237112, 56.48838694879862 ], [ 25.594644809910051, 56.467147936128299 ], [ 25.590717401287009, 56.460843410694793 ], [ 25.583431023922572, 56.458569648470473 ], [ 25.573819206591111, 56.452859402239596 ], [ 25.566687859756883, 56.447614244002125 ], [ 25.547774286154379, 56.444875392885024 ], [ 25.523538039049242, 56.444823716940959 ], [ 25.505399610903282, 56.436400458014703 ], [ 25.476202427300166, 56.418882148392413 ], [ 25.455583529556236, 56.413352770214146 ], [ 25.393520135246035, 56.410975654303002 ], [ 25.331301711304889, 56.421285101825958 ], [ 25.296213413418286, 56.420820013832554 ], [ 25.29357791508869, 56.407900906401665 ], [ 25.133296972777032, 56.413429173017903 ], [ 25.074298280041546, 56.4027021378933 ], [ 25.051056371054642, 56.463488670265917 ], [ 25.027814462067738, 56.459912991891031 ], [ 25.013511748568305, 56.495669774740293 ], [ 24.995633356694043, 56.551092789100892 ], [ 24.956300895469951, 56.551092789100892 ], [ 24.949987336346453, 56.578568175680743 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-073", "NAME_1": "Preilu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.748166130962147, 56.111097316987582 ], [ 26.718503859365626, 56.111252345719151 ], [ 26.700365431219666, 56.118693752714478 ], [ 26.687342970101952, 56.12551504208551 ], [ 26.647913853341663, 56.135281887249278 ], [ 26.617752225085098, 56.149984271157166 ], [ 26.603277738376903, 56.166526542066208 ], [ 26.646701199400809, 56.164458757865361 ], [ 26.671514605314769, 56.176865461271973 ], [ 26.673582389515673, 56.218221138994352 ], [ 26.659107901908101, 56.259576815817354 ], [ 26.640497847697475, 56.276119086726453 ], [ 26.601209954175999, 56.282322438429787 ], [ 26.590871034970291, 56.319542547750359 ], [ 26.570193196558762, 56.321610331051886 ], [ 26.514363032128188, 56.334017034458554 ], [ 26.460600651898574, 56.358830440372515 ], [ 26.479210707008519, 56.391914982190656 ], [ 26.541244222243051, 56.375372711281557 ], [ 26.576396548262096, 56.433270659912978 ], [ 26.590871034970291, 56.445677362420327 ], [ 26.623955576788433, 56.449812930822077 ], [ 26.685989092022965, 56.429135091511228 ], [ 26.733548121448621, 56.396050550592406 ], [ 26.710802498836244, 56.383643847185795 ], [ 26.669446821113866, 56.369169360477599 ], [ 26.65083676690324, 56.336084818659401 ], [ 26.665311253611435, 56.321610331051886 ], [ 26.702531362932064, 56.313339196047025 ], [ 26.741819256453482, 56.290593573434649 ], [ 26.783174933276541, 56.282322438429787 ], [ 26.814191691793098, 56.253373464114077 ], [ 26.839005097707059, 56.214085570592601 ], [ 26.834869530204628, 56.199611083884406 ], [ 26.801784988386487, 56.185136597176211 ], [ 26.826598394300447, 56.176865461271973 ], [ 26.861750720319492, 56.162390974563777 ], [ 26.882428558730965, 56.152052055358013 ], [ 26.888467644765967, 56.128874009927586 ], [ 26.872292922280508, 56.12722036442824 ], [ 26.847643263126088, 56.12396474937367 ], [ 26.836791213243885, 56.129390773865111 ], [ 26.823251987289382, 56.12951996507428 ], [ 26.807283970378933, 56.127323717215745 ], [ 26.774727817135044, 56.115076402454008 ], [ 26.748166130962147, 56.111097316987582 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-056", "NAME_1": "Livanu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.33542646695787, 56.189748846731845 ], [ 26.300803257064615, 56.18334096941021 ], [ 26.264268019253677, 56.210781154726476 ], [ 26.249436883005728, 56.216465563434951 ], [ 26.239204949848556, 56.217447415365882 ], [ 26.219361200259186, 56.22168488235144 ], [ 26.208147414271707, 56.222666734282313 ], [ 26.193522983598768, 56.22607737896783 ], [ 26.189027134194873, 56.234733180991498 ], [ 26.19109419084424, 56.257393296963073 ], [ 26.19243777798107, 56.262612616778824 ], [ 26.195279981885619, 56.268762112581385 ], [ 26.197708774640205, 56.275893460314933 ], [ 26.197915480215215, 56.28411001186754 ], [ 26.196210157872486, 56.283334866410939 ], [ 26.191714307569214, 56.285401923060249 ], [ 26.186908399802803, 56.28855418622669 ], [ 26.184272902372527, 56.290931301238516 ], [ 26.183187696754771, 56.296047268266761 ], [ 26.184272902372527, 56.315477606706168 ], [ 26.181068963262021, 56.320800279309424 ], [ 26.166961297425928, 56.337181708269213 ], [ 26.163809035158806, 56.342452704029029 ], [ 26.162878859171997, 56.36332998329209 ], [ 26.160760124779927, 56.372270006155816 ], [ 26.156987745787831, 56.380331528976853 ], [ 26.144688755081972, 56.389245714318236 ], [ 26.108876986783514, 56.405859687274699 ], [ 26.088516473256618, 56.406428128055666 ], [ 26.153370396426624, 56.429269111180531 ], [ 26.191249219575809, 56.457872016480337 ], [ 26.199465772927056, 56.473039049512522 ], [ 26.232848747571609, 56.498825589329442 ], [ 26.25718834836357, 56.512003079178726 ], [ 26.290416294276554, 56.517894192562892 ], [ 26.309174839147488, 56.515620429439252 ], [ 26.315686069256685, 56.511951402335342 ], [ 26.320802036284931, 56.50688711215048 ], [ 26.323282504983581, 56.498179633283428 ], [ 26.353099806211048, 56.474537665380922 ], [ 26.362556593911677, 56.450844021534351 ], [ 26.412372674359403, 56.465313422576344 ], [ 26.43583377510862, 56.475932929361193 ], [ 26.445600620272387, 56.482650865045343 ], [ 26.45800296376575, 56.488903714534786 ], [ 26.475211215924844, 56.494071357507096 ], [ 26.498982375036576, 56.495931708581452 ], [ 26.529058057783118, 56.493606269513691 ], [ 26.540530226188991, 56.489678859991386 ], [ 26.537108654740621, 56.466355201731119 ], [ 26.537108654740621, 56.445677362420327 ], [ 26.559854277352997, 56.439474011616312 ], [ 26.576396548262096, 56.433270659912978 ], [ 26.541244222243051, 56.375372711281557 ], [ 26.479210707008519, 56.391914982190656 ], [ 26.460600651898574, 56.358830440372515 ], [ 26.514363032128188, 56.334017034458554 ], [ 26.570193196558762, 56.321610331051886 ], [ 26.590871034970291, 56.319542547750359 ], [ 26.601209954175999, 56.282322438429787 ], [ 26.561922060654581, 56.267847950822272 ], [ 26.510227464625757, 56.267847950822272 ], [ 26.441990597687891, 56.265780167520688 ], [ 26.402702704166472, 56.282322438429787 ], [ 26.379957081554039, 56.313339196047025 ], [ 26.355143675640079, 56.311271411846178 ], [ 26.353075891439232, 56.27405130252555 ], [ 26.293110159506227, 56.284390221731314 ], [ 26.311720214616173, 56.226492273999213 ], [ 26.33542646695787, 56.189748846731845 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-103", "NAME_1": "Varkavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.507508985851018, 56.132336331456486 ], [ 26.484512973994526, 56.167786362750405 ], [ 26.455367466335474, 56.180447089561483 ], [ 26.41655846540084, 56.183547674985164 ], [ 26.380850050789206, 56.196906032887057 ], [ 26.33542646695787, 56.189748846731845 ], [ 26.311720214616173, 56.226492273999213 ], [ 26.293110159506227, 56.284390221731314 ], [ 26.353075891439232, 56.27405130252555 ], [ 26.355143675640079, 56.311271411846178 ], [ 26.379957081554039, 56.313339196047025 ], [ 26.402702704166472, 56.282322438429787 ], [ 26.441990597687891, 56.265780167520688 ], [ 26.510227464625757, 56.267847950822272 ], [ 26.561922060654581, 56.267847950822272 ], [ 26.601209954175999, 56.282322438429787 ], [ 26.640497847697475, 56.276119086726453 ], [ 26.659107901908101, 56.259576815817354 ], [ 26.673582389515673, 56.218221138994352 ], [ 26.671514605314769, 56.176865461271973 ], [ 26.646701199400809, 56.164458757865361 ], [ 26.603277738376903, 56.166526542066208 ], [ 26.617752225085098, 56.149984271157166 ], [ 26.647913853341663, 56.135281887249278 ], [ 26.624814486899027, 56.121122545469063 ], [ 26.62062869585759, 56.1249466013046 ], [ 26.609621616344498, 56.132207140247317 ], [ 26.576238640800625, 56.14804596594854 ], [ 26.570657585778918, 56.153962917754427 ], [ 26.556549919942825, 56.156572578561622 ], [ 26.543940870874451, 56.142103175720933 ], [ 26.54084028455145, 56.133912461690727 ], [ 26.507508985851018, 56.132336331456486 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-JKB", "NAME_1": "Jekabpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.088516473256618, 56.406428128055666 ], [ 26.108876986783514, 56.405859687274699 ], [ 26.144688755081972, 56.389245714318236 ], [ 26.156987745787831, 56.380331528976853 ], [ 26.160760124779927, 56.372270006155816 ], [ 26.162878859171997, 56.36332998329209 ], [ 26.163809035158806, 56.342452704029029 ], [ 26.166961297425928, 56.337181708269213 ], [ 26.181068963262021, 56.320800279309424 ], [ 26.184272902372527, 56.315477606706168 ], [ 26.183187696754771, 56.296047268266761 ], [ 26.184272902372527, 56.290931301238516 ], [ 26.186908399802803, 56.28855418622669 ], [ 26.191714307569214, 56.285401923060249 ], [ 26.196210157872486, 56.283334866410939 ], [ 26.197915480215215, 56.28411001186754 ], [ 26.197708774640205, 56.275893460314933 ], [ 26.195279981885619, 56.268762112581385 ], [ 26.19243777798107, 56.262612616778824 ], [ 26.19109419084424, 56.257393296963073 ], [ 26.189027134194873, 56.234733180991498 ], [ 26.193522983598768, 56.22607737896783 ], [ 26.208147414271707, 56.222666734282313 ], [ 26.219361200259186, 56.22168488235144 ], [ 26.239204949848556, 56.217447415365882 ], [ 26.249436883005728, 56.216465563434951 ], [ 26.264268019253677, 56.210781154726476 ], [ 26.300803257064615, 56.18334096941021 ], [ 26.328295119224379, 56.162670397520799 ], [ 26.33656334762037, 56.149441229928811 ], [ 26.340749138661806, 56.134248359374283 ], [ 26.342609490635482, 56.107893377877076 ], [ 26.345348341752526, 56.093449815256747 ], [ 26.316306186880979, 56.122724514125025 ], [ 26.315065951632334, 56.130734361001942 ], [ 26.31863162594874, 56.134997667308483 ], [ 26.320698682598106, 56.141534735839343 ], [ 26.317546421230361, 56.14592723245579 ], [ 26.313050570927089, 56.150371405915621 ], [ 26.304989048106052, 56.153471992238622 ], [ 26.282199740925307, 56.158949693573447 ], [ 26.25718834836357, 56.162076118318168 ], [ 26.243390740889936, 56.167062893237926 ], [ 26.219671257722325, 56.166029365362874 ], [ 26.205821974304627, 56.163600571708969 ], [ 26.138952671328695, 56.131096096207841 ], [ 26.151355014822059, 56.121019191782239 ], [ 26.153060337164789, 56.118280341564457 ], [ 26.152440220439814, 56.109185289069785 ], [ 26.129340854896498, 56.098384915131703 ], [ 26.120504184820277, 56.100710354199464 ], [ 26.108566929320318, 56.101485501454704 ], [ 26.097559848907906, 56.096007799220558 ], [ 26.092392205935539, 56.089599920999547 ], [ 26.079369744817825, 56.078799547061408 ], [ 26.058905876704841, 56.067740789805555 ], [ 26.046296827636468, 56.06634552672466 ], [ 26.036995069566785, 56.068567613004916 ], [ 26.025522902060288, 56.079419663786439 ], [ 26.012188109093927, 56.108357187034528 ], [ 25.98547398333767, 56.110583363431431 ], [ 25.976569275051986, 56.128392780902061 ], [ 25.976569275051986, 56.148428375668971 ], [ 25.94762897199945, 56.157333083954654 ], [ 25.905331606068728, 56.170690146832783 ], [ 25.896426897783101, 56.201856626282222 ], [ 25.876391303016192, 56.221892221049131 ], [ 25.838546291678028, 56.226344574742313 ], [ 25.800701280339808, 56.23524928392726 ], [ 25.756177738012184, 56.230796929334815 ], [ 25.693844778213929, 56.228570752038536 ], [ 25.680487715335801, 56.29312988913307 ], [ 25.733915966848372, 56.299808421021794 ], [ 25.736142143245274, 56.328748723175011 ], [ 25.733915966848372, 56.351010495238143 ], [ 25.794022749350404, 56.359915203523769 ], [ 25.842998646270473, 56.413343455036397 ], [ 25.809605989524755, 56.444509934485836 ], [ 25.820736875106661, 56.486807300416558 ], [ 25.845224823566753, 56.477902592130874 ], [ 25.883069834904916, 56.475676414834652 ], [ 25.876556836728639, 56.495306707638406 ], [ 25.925696060424855, 56.483753711764336 ], [ 26.043223503361958, 56.456122137226828 ], [ 26.055267774588117, 56.451361395212189 ], [ 26.06405683725319, 56.436835028413782 ], [ 26.080332878259696, 56.417914129833207 ], [ 26.088516473256618, 56.406428128055666 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-049", "NAME_1": "Krustpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.362556593911677, 56.450844021534351 ], [ 26.353099806211048, 56.474537665380922 ], [ 26.323282504983581, 56.498179633283428 ], [ 26.320802036284931, 56.50688711215048 ], [ 26.315686069256685, 56.511951402335342 ], [ 26.309174839147488, 56.515620429439252 ], [ 26.290416294276554, 56.517894192562892 ], [ 26.25718834836357, 56.512003079178726 ], [ 26.232848747571609, 56.498825589329442 ], [ 26.199465772927056, 56.473039049512522 ], [ 26.191249219575809, 56.457872016480337 ], [ 26.153370396426624, 56.429269111180531 ], [ 26.088516473256618, 56.406428128055666 ], [ 26.080332878259696, 56.417914129833207 ], [ 26.06405683725319, 56.436835028413782 ], [ 26.055267774588117, 56.451361395212189 ], [ 26.043223503361958, 56.456122137226828 ], [ 25.925696060424855, 56.483753711764336 ], [ 25.876556836728639, 56.495306707638406 ], [ 25.925367200835638, 56.513521426172872 ], [ 25.918688668946913, 56.529104666347223 ], [ 25.889748365894377, 56.522426134458499 ], [ 25.878617480312471, 56.535783197336684 ], [ 25.854129531852379, 56.535783197336684 ], [ 25.826426628486104, 56.527167059012527 ], [ 25.79973392008435, 56.527696030347499 ], [ 25.786875846942792, 56.530340887921625 ], [ 25.7692163419606, 56.554144597994764 ], [ 25.748057487662891, 56.566351629424105 ], [ 25.756108922788542, 56.592025575919592 ], [ 25.756908806795991, 56.612255356799267 ], [ 25.788276400735299, 56.609309801006475 ], [ 25.830961135248174, 56.613237210528837 ], [ 25.883257684394607, 56.623572496473514 ], [ 25.93209191291146, 56.637835191041233 ], [ 25.983613315701916, 56.665637112462719 ], [ 26.002113478154456, 56.639695543014852 ], [ 26.022887403730692, 56.629308580226791 ], [ 26.039992303102281, 56.626466376322185 ], [ 26.058130731248241, 56.629954536272805 ], [ 26.09223717630465, 56.643881334056289 ], [ 26.131046177239284, 56.653906562537827 ], [ 26.156470981850305, 56.650134181747035 ], [ 26.170010206905488, 56.645018216517428 ], [ 26.19109419084424, 56.642331041344448 ], [ 26.204891799217137, 56.635251370454341 ], [ 26.198225538577731, 56.6271381698906 ], [ 26.224632196019002, 56.609878241787385 ], [ 26.261994256129981, 56.616570339949192 ], [ 26.281269565837761, 56.610911770561756 ], [ 26.284215121630552, 56.605795803533454 ], [ 26.291346470263363, 56.58670136187834 ], [ 26.342712844322307, 56.60383209877233 ], [ 26.350774367143288, 56.60998159457489 ], [ 26.375579054130014, 56.635923163123437 ], [ 26.401365593946991, 56.633856106474127 ], [ 26.484512973994526, 56.60613170031769 ], [ 26.522546827673921, 56.599853014205223 ], [ 26.593756952221554, 56.595150458326941 ], [ 26.583369989433436, 56.563524481969239 ], [ 26.586625603588743, 56.551432196838391 ], [ 26.595927361658369, 56.547039700221944 ], [ 26.608536410726742, 56.544714260254864 ], [ 26.627760043591138, 56.543732408323933 ], [ 26.595152215302448, 56.506551215366244 ], [ 26.593756952221554, 56.501590277968944 ], [ 26.593136833697884, 56.496603502149867 ], [ 26.59732262473932, 56.495569973375552 ], [ 26.597012567276124, 56.494536445500557 ], [ 26.59639244965183, 56.488800360847961 ], [ 26.559495476634936, 56.486164863417685 ], [ 26.540530226188991, 56.489678859991386 ], [ 26.529058057783118, 56.493606269513691 ], [ 26.498982375036576, 56.495931708581452 ], [ 26.475211215924844, 56.494071357507096 ], [ 26.45800296376575, 56.488903714534786 ], [ 26.445600620272387, 56.482650865045343 ], [ 26.43583377510862, 56.475932929361193 ], [ 26.412372674359403, 56.465313422576344 ], [ 26.362556593911677, 56.450844021534351 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-086", "NAME_1": "Salas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.744041375309223, 56.617267971040008 ], [ 25.756908806795991, 56.612255356799267 ], [ 25.756108922788542, 56.592025575919592 ], [ 25.748057487662891, 56.566351629424105 ], [ 25.7692163419606, 56.554144597994764 ], [ 25.786875846942792, 56.530340887921625 ], [ 25.79973392008435, 56.527696030347499 ], [ 25.826426628486104, 56.527167059012527 ], [ 25.820736875106661, 56.486807300416558 ], [ 25.809605989524755, 56.444509934485836 ], [ 25.842998646270473, 56.413343455036397 ], [ 25.794022749350404, 56.359915203523769 ], [ 25.733915966848372, 56.351010495238143 ], [ 25.727237434959648, 56.368819911809453 ], [ 25.676035360743299, 56.36659373541255 ], [ 25.658225943272669, 56.393307860269488 ], [ 25.635964172108856, 56.408891100443896 ], [ 25.611476223648765, 56.431152872507027 ], [ 25.615928578241267, 56.446736111782116 ], [ 25.594644809910051, 56.467147936128299 ], [ 25.580020379237112, 56.48838694879862 ], [ 25.566067743031908, 56.498308824492653 ], [ 25.558626336036582, 56.510659492041896 ], [ 25.575989617826565, 56.539701646913443 ], [ 25.598675571320541, 56.551819770466011 ], [ 25.605806919054032, 56.562025865201463 ], [ 25.607202183034303, 56.567710273010675 ], [ 25.599450717676461, 56.57029409449683 ], [ 25.580175408868001, 56.569157212935011 ], [ 25.560331659278631, 56.573859767913916 ], [ 25.569788446079883, 56.576701971818522 ], [ 25.579400261612761, 56.576288561567821 ], [ 25.601466099281026, 56.578924058998098 ], [ 25.646734652582154, 56.596390693575643 ], [ 25.688334180577954, 56.600783190192033 ], [ 25.711330194233085, 56.605847480376895 ], [ 25.744041375309223, 56.617267971040008 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-003", "NAME_1": "Aizputes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.436449008614829, 56.867588609426775 ], [ 21.493086379332908, 56.864694729578105 ], [ 21.515772331927565, 56.84397247994599 ], [ 21.526004265984056, 56.842034614505849 ], [ 21.566673617993047, 56.842215480759819 ], [ 21.593028598590934, 56.84513519992953 ], [ 21.608686558038187, 56.832887885167736 ], [ 21.610288526694148, 56.822733466376349 ], [ 21.622535842355205, 56.81136465075798 ], [ 21.629667189189433, 56.799892483251483 ], [ 21.644239943018931, 56.791159165962711 ], [ 21.651681349114938, 56.790073961244275 ], [ 21.673747185883883, 56.790590725181801 ], [ 21.680103387261511, 56.791572578012051 ], [ 21.738446079422999, 56.764132391796409 ], [ 21.761235385704481, 56.741188055884038 ], [ 21.762785679315641, 56.731989651501181 ], [ 21.770537143774163, 56.723023790215734 ], [ 21.771777378123488, 56.713024400155916 ], [ 21.767591587082052, 56.704782010181589 ], [ 21.776118197896494, 56.698658351901372 ], [ 21.801439649720066, 56.690648505024456 ], [ 21.822988721652166, 56.68785797796329 ], [ 21.809346143809478, 56.66933197708903 ], [ 21.815237258092964, 56.661916409414744 ], [ 21.824383985632437, 56.647136949110859 ], [ 21.820508253852836, 56.639152939756343 ], [ 21.813841994112749, 56.62964447701097 ], [ 21.802989943331227, 56.622849026061658 ], [ 21.776738316420165, 56.613702298522242 ], [ 21.777720168351095, 56.6101883001499 ], [ 21.790587598938544, 56.597062486244738 ], [ 21.795238477973385, 56.588639228217801 ], [ 21.793688185261544, 56.58341990840205 ], [ 21.795393507604274, 56.573343003976447 ], [ 21.752952162012662, 56.562744378550178 ], [ 21.712444082728211, 56.549952353418348 ], [ 21.661275982200777, 56.560612373762012 ], [ 21.584523831409683, 56.549952353418348 ], [ 21.571731806277796, 56.560612373762012 ], [ 21.618635898128161, 56.588328428813895 ], [ 21.618635898128161, 56.613912479077612 ], [ 21.607975876885178, 56.643760537119078 ], [ 21.571731806277796, 56.658684567039131 ], [ 21.518431701861516, 56.652288554473159 ], [ 21.490715646809633, 56.652288554473159 ], [ 21.452339571414086, 56.645892541907244 ], [ 21.431019529827438, 56.673608596059807 ], [ 21.403303475674818, 56.671476592170961 ], [ 21.399039466997806, 56.692796633757609 ], [ 21.337211346126708, 56.718380684021326 ], [ 21.31589130364074, 56.739700725607975 ], [ 21.324419320994878, 56.758888763305777 ], [ 21.320155312317809, 56.807924859944308 ], [ 21.345739362581526, 56.820716885076138 ], [ 21.40969948824079, 56.831376905419802 ], [ 21.439547546282199, 56.83990492277394 ], [ 21.436449008614829, 56.867588609426775 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-050", "NAME_1": "Kuldigas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.526004265984056, 56.842034614505849 ], [ 21.515772331927565, 56.84397247994599 ], [ 21.493086379332908, 56.864694729578105 ], [ 21.436449008614829, 56.867588609426775 ], [ 21.467971633084403, 56.878905748201703 ], [ 21.473707716837623, 56.887587389546354 ], [ 21.493396436796104, 56.903813787975935 ], [ 21.518585955377546, 56.910588849495753 ], [ 21.564311109769903, 56.910588849495753 ], [ 21.584887429111575, 56.931165168837424 ], [ 21.639757614022642, 56.9334514262423 ], [ 21.664906449073328, 56.949455230774277 ], [ 21.706059087756671, 56.956314003888167 ], [ 21.69234154152889, 56.997466642571453 ], [ 21.655761418554619, 57.008897931394415 ], [ 21.632898840908751, 57.034046766445101 ], [ 21.639757614022642, 57.047764312672882 ], [ 21.60546374845319, 57.061481858900663 ], [ 21.573804965726595, 57.085611476988049 ], [ 21.593028598590934, 57.087265123386715 ], [ 21.626256545403237, 57.104059964395788 ], [ 21.641449415957766, 57.114214383187232 ], [ 21.653851760350449, 57.116152249526692 ], [ 21.664083692608301, 57.116307278258262 ], [ 21.692350702023248, 57.114214383187232 ], [ 21.711574333988324, 57.116849881516771 ], [ 21.766971470357078, 57.107057197031963 ], [ 21.789192335857592, 57.104111640339909 ], [ 21.789812452582623, 57.099822496510967 ], [ 21.789502394220108, 57.095274970263631 ], [ 21.783094516898416, 57.085198065838028 ], [ 21.82190351693373, 57.085275580203813 ], [ 21.839938592292185, 57.093001207139991 ], [ 21.849860467086842, 57.098427232530753 ], [ 21.86009240114339, 57.099305732573498 ], [ 21.936573520551519, 57.097238675024812 ], [ 21.951973096681058, 57.098427232530753 ], [ 21.996466506324168, 57.107367255394479 ], [ 22.015276727139224, 57.113723455872787 ], [ 22.075324740744179, 57.101992905947839 ], [ 22.095168491232869, 57.101631170741882 ], [ 22.172734816258753, 57.116565659777336 ], [ 22.192475213060618, 57.124575507553629 ], [ 22.194335565034294, 57.128089505026594 ], [ 22.195265741021103, 57.132094428015421 ], [ 22.195110711390214, 57.149070136177841 ], [ 22.195575799383619, 57.153385118428446 ], [ 22.196350945739539, 57.157545071048162 ], [ 22.199606560794109, 57.166123358705988 ], [ 22.206427850165142, 57.177853909530256 ], [ 22.234694857781449, 57.170541693744156 ], [ 22.24182620641426, 57.166691800386275 ], [ 22.249887730134617, 57.155271307924465 ], [ 22.262755160722065, 57.145013536345573 ], [ 22.297998488239614, 57.128761297695689 ], [ 22.327609083892071, 57.120415554034537 ], [ 22.348899774305096, 57.11010610651158 ], [ 22.354170770064968, 57.102664700415573 ], [ 22.363007440141246, 57.078738512572272 ], [ 22.359286737093214, 57.056465969328997 ], [ 22.35959679455641, 57.051453355987576 ], [ 22.362697380879411, 57.042409980336288 ], [ 22.372619255674067, 57.035769558118602 ], [ 22.382386101737211, 57.019672349998928 ], [ 22.412668490958083, 57.014763089445012 ], [ 22.480002882826739, 57.017760322081187 ], [ 22.508218215398301, 57.013832913458145 ], [ 22.472406447099843, 56.982465317720198 ], [ 22.455921665352548, 56.940400703529633 ], [ 22.445379672933541, 56.938540351555957 ], [ 22.454888135678914, 56.917094632411306 ], [ 22.443364292228296, 56.903555406456803 ], [ 22.432357211815884, 56.898542792216062 ], [ 22.417784457986329, 56.889344386933885 ], [ 22.392307977431187, 56.885882066304305 ], [ 22.348744744674207, 56.896424058723312 ], [ 22.338151076311078, 56.904072171293649 ], [ 22.338512810617715, 56.908025418338354 ], [ 22.335670606713109, 56.913012193258112 ], [ 22.328384230247991, 56.916836249093592 ], [ 22.304354688717865, 56.919730129841582 ], [ 22.290247022881772, 56.91006663746532 ], [ 22.287869906970627, 56.905777492737059 ], [ 22.267406039756906, 56.898542792216062 ], [ 22.26399539597071, 56.893943590024605 ], [ 22.269731479723987, 56.888052477539759 ], [ 22.287869906970627, 56.883091539243139 ], [ 22.295518018641644, 56.883298244818093 ], [ 22.30388960072446, 56.884512640745754 ], [ 22.310555861363866, 56.883763332811554 ], [ 22.32032270742701, 56.880404364969422 ], [ 22.329469434966427, 56.87477163400365 ], [ 22.342026808090679, 56.864229640685323 ], [ 22.34130333767888, 56.847486477418954 ], [ 22.316602004378979, 56.843662421583474 ], [ 22.305749952698136, 56.838623968921013 ], [ 22.269731479723987, 56.82970978537827 ], [ 22.251127963584622, 56.823095201582248 ], [ 22.230302362064322, 56.80906505011194 ], [ 22.204877556553981, 56.795215765794921 ], [ 22.162657911833151, 56.795034897742312 ], [ 22.113012961578875, 56.807707251888132 ], [ 22.09243664223726, 56.823711056420109 ], [ 22.030707684212246, 56.823711056420109 ], [ 21.982696271515692, 56.821424798115913 ], [ 21.934684859718459, 56.798562221369366 ], [ 21.959833694769202, 56.732260747635394 ], [ 21.930112344009444, 56.718543201407613 ], [ 21.886673447921282, 56.720829458812432 ], [ 21.840948293528925, 56.704825654280512 ], [ 21.822988721652166, 56.68785797796329 ], [ 21.801439649720066, 56.690648505024456 ], [ 21.776118197896494, 56.698658351901372 ], [ 21.767591587082052, 56.704782010181589 ], [ 21.771777378123488, 56.713024400155916 ], [ 21.770537143774163, 56.723023790215734 ], [ 21.762785679315641, 56.731989651501181 ], [ 21.761235385704481, 56.741188055884038 ], [ 21.738446079422999, 56.764132391796409 ], [ 21.680103387261511, 56.791572578012051 ], [ 21.673747185883883, 56.790590725181801 ], [ 21.651681349114938, 56.790073961244275 ], [ 21.644239943018931, 56.791159165962711 ], [ 21.629667189189433, 56.799892483251483 ], [ 21.622535842355205, 56.81136465075798 ], [ 21.610288526694148, 56.822733466376349 ], [ 21.608686558038187, 56.832887885167736 ], [ 21.593028598590934, 56.84513519992953 ], [ 21.566673617993047, 56.842215480759819 ], [ 21.526004265984056, 56.842034614505849 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-006", "NAME_1": "Alsungas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.493396436796104, 56.903813787975935 ], [ 21.447301060295672, 56.912082018170565 ], [ 21.406063266606452, 56.923450832889614 ], [ 21.397226597429494, 56.929393622217844 ], [ 21.391490512776954, 56.934483750824427 ], [ 21.39583133344928, 56.942416083335559 ], [ 21.446680941772001, 56.949650783856555 ], [ 21.454329054342395, 56.960296129063067 ], [ 21.472002393595574, 56.971354885419601 ], [ 21.474947951187005, 56.986444404085944 ], [ 21.473552687206734, 57.0129544134154 ], [ 21.484869825981662, 57.028509020075205 ], [ 21.489210645754667, 57.051014106415835 ], [ 21.503008254127622, 57.058274645358551 ], [ 21.506160516394687, 57.075612087827551 ], [ 21.539026727101771, 57.07057363516509 ], [ 21.573804965726595, 57.085611476988049 ], [ 21.60546374845319, 57.061481858900663 ], [ 21.639757614022642, 57.047764312672882 ], [ 21.632898840908751, 57.034046766445101 ], [ 21.655761418554619, 57.008897931394415 ], [ 21.69234154152889, 56.997466642571453 ], [ 21.706059087756671, 56.956314003888167 ], [ 21.664906449073328, 56.949455230774277 ], [ 21.639757614022642, 56.9334514262423 ], [ 21.584887429111575, 56.931165168837424 ], [ 21.564311109769903, 56.910588849495753 ], [ 21.518585955377546, 56.910588849495753 ], [ 21.493396436796104, 56.903813787975935 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-093", "NAME_1": "Skrundas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.866138543259126, 56.520426337205663 ], [ 21.850790643073708, 56.521149806718199 ], [ 21.833065626977088, 56.523966173100405 ], [ 21.818027785154129, 56.530839138415502 ], [ 21.807485792735122, 56.538461412564118 ], [ 21.793223097268083, 56.554377753530446 ], [ 21.795393507604274, 56.573343003976447 ], [ 21.793688185261544, 56.58341990840205 ], [ 21.795238477973385, 56.588639228217801 ], [ 21.790587598938544, 56.597062486244738 ], [ 21.777720168351095, 56.6101883001499 ], [ 21.776738316420165, 56.613702298522242 ], [ 21.802989943331227, 56.622849026061658 ], [ 21.813841994112749, 56.62964447701097 ], [ 21.820508253852836, 56.639152939756343 ], [ 21.824383985632437, 56.647136949110859 ], [ 21.815237258092964, 56.661916409414744 ], [ 21.809346143809478, 56.66933197708903 ], [ 21.822988721652166, 56.68785797796329 ], [ 21.840948293528925, 56.704825654280512 ], [ 21.886673447921282, 56.720829458812432 ], [ 21.930112344009444, 56.718543201407613 ], [ 21.959833694769202, 56.732260747635394 ], [ 21.934684859718459, 56.798562221369366 ], [ 21.982696271515692, 56.821424798115913 ], [ 22.030707684212246, 56.823711056420109 ], [ 22.09243664223726, 56.823711056420109 ], [ 22.113012961578875, 56.807707251888132 ], [ 22.162657911833151, 56.795034897742312 ], [ 22.174491815444924, 56.765889390982579 ], [ 22.185963982951421, 56.748112698042576 ], [ 22.189219598006048, 56.741756497564268 ], [ 22.188909538744213, 56.735658677705771 ], [ 22.181003044654744, 56.72852732997228 ], [ 22.173975050608078, 56.715969956848028 ], [ 22.166998731606157, 56.708218492389506 ], [ 22.155991652093064, 56.700311998300037 ], [ 22.125450881353061, 56.69013174108693 ], [ 22.112221713761073, 56.682948717409374 ], [ 22.103540073315742, 56.672871812983772 ], [ 22.111446567405153, 56.646671861117454 ], [ 22.111911655398558, 56.625691229966264 ], [ 22.109276157068962, 56.613624783257137 ], [ 22.112531773022909, 56.609516507480805 ], [ 22.115373976028138, 56.603806261249929 ], [ 22.118577915138644, 56.592359931265776 ], [ 22.123435499748496, 56.58763153696583 ], [ 22.126070998078092, 56.582205512474388 ], [ 22.121265090311624, 56.576340237511943 ], [ 22.099974399898599, 56.56853709620998 ], [ 22.060596958183055, 56.564506333900169 ], [ 22.011400995359622, 56.555049547098918 ], [ 22.011711052822818, 56.547504788215349 ], [ 22.018222283831335, 56.542595526762113 ], [ 22.049073113833117, 56.529056300807611 ], [ 22.05744469591599, 56.523268541110269 ], [ 22.056049431935719, 56.509057522486671 ], [ 22.037445915796354, 56.504044908245874 ], [ 22.024268425947071, 56.50745555293139 ], [ 22.009695672117573, 56.507972316868916 ], [ 21.99786176940512, 56.50593109774195 ], [ 21.966700881040822, 56.506628729732086 ], [ 21.956934034977735, 56.514173489514917 ], [ 21.920605502741751, 56.506938788094544 ], [ 21.912388950289824, 56.507920640924794 ], [ 21.910063511222063, 56.512054755122847 ], [ 21.908358188879276, 56.517454942091888 ], [ 21.904172397837897, 56.521511541924099 ], [ 21.893475375787943, 56.523010158691818 ], [ 21.866138543259126, 56.520426337205663 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-043", "NAME_1": "Kandavas" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.454888135678914, 56.917094632411306 ], [ 22.445379672933541, 56.938540351555957 ], [ 22.455921665352548, 56.940400703529633 ], [ 22.472406447099843, 56.982465317720198 ], [ 22.508218215398301, 57.013832913458145 ], [ 22.559584587658549, 57.008381048746401 ], [ 22.591417271389957, 57.022385361794989 ], [ 22.635807326446923, 57.015848294163391 ], [ 22.649449904289611, 57.041660672402088 ], [ 22.6415434102002, 57.094732367904442 ], [ 22.669603713140816, 57.097703762118897 ], [ 22.76086429195351, 57.112328192791836 ], [ 22.793265414667133, 57.126022447477965 ], [ 22.801998731955905, 57.131345120081221 ], [ 22.80804487497096, 57.136616115841036 ], [ 22.831299269245847, 57.140078437369937 ], [ 22.911242710182933, 57.137184557521266 ], [ 22.930931431040676, 57.144574286773889 ], [ 22.955708872166724, 57.126776999358981 ], [ 22.941406158667291, 57.11426212549668 ], [ 22.912800732567746, 57.103535090372077 ], [ 22.896710179431238, 57.089232376872644 ], [ 22.841287165969902, 57.078505341748098 ], [ 22.855589879469335, 57.057051271498949 ], [ 22.866316913694618, 57.037385040886875 ], [ 22.878831788456239, 57.019506649911932 ], [ 22.862741235319731, 57.010567453525141 ], [ 22.830560130845356, 57.006991775150254 ], [ 22.818045256982998, 56.974810670675879 ], [ 22.830560130845356, 56.9497809220519 ], [ 22.873468270444334, 56.924751173427865 ], [ 22.916376410942576, 56.915811977940393 ], [ 22.918164249680387, 56.888994390578659 ], [ 22.923527767692292, 56.874691677079227 ], [ 22.943193998304366, 56.855025446467153 ], [ 22.907437214555785, 56.853237607729397 ], [ 22.875256110081409, 56.858601124841982 ], [ 22.835923647957998, 56.849661929354511 ], [ 22.846650683082544, 56.828207859105362 ], [ 22.816257417345923, 56.813905145605929 ], [ 22.788924594894127, 56.803871567818589 ], [ 22.718799675964249, 56.802011216744233 ], [ 22.679060499942125, 56.809555976527065 ], [ 22.642008498193604, 56.808212389390235 ], [ 22.645884229973205, 56.816351427476377 ], [ 22.634257032835762, 56.82412873035662 ], [ 22.586301304361712, 56.837151191474277 ], [ 22.527648552938331, 56.841595364034788 ], [ 22.490854932708942, 56.851517238829501 ], [ 22.500466750040459, 56.862989407235318 ], [ 22.506564568999636, 56.86748525753859 ], [ 22.514626091820674, 56.871981106043165 ], [ 22.518966913392319, 56.876838691552337 ], [ 22.52113732282919, 56.88464183195498 ], [ 22.520982293198244, 56.900299791402233 ], [ 22.524392937883761, 56.905105699168644 ], [ 22.529198845650171, 56.908774726272554 ], [ 22.526563348219952, 56.912960517313991 ], [ 22.511938918446333, 56.917404689874502 ], [ 22.492405226320102, 56.920091865047482 ], [ 22.454888135678914, 56.917094632411306 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-099", "NAME_1": "Tukums" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.492034132569131, 56.934638780455316 ], [ 23.506761916029575, 56.894615384492397 ], [ 23.461958448923269, 56.884228420804959 ], [ 23.48660810717837, 56.815912177005316 ], [ 23.451674838922656, 56.799401556836358 ], [ 23.408835076577532, 56.792399400312036 ], [ 23.397207879440089, 56.792244371580466 ], [ 23.389456414082247, 56.793975532344916 ], [ 23.375038689883638, 56.800770982394909 ], [ 23.321966994381285, 56.782115790311423 ], [ 23.315920851366172, 56.774157620277947 ], [ 23.314990676278683, 56.765475978933239 ], [ 23.325222609435855, 56.749456285179406 ], [ 23.299642775193888, 56.715349840122997 ], [ 23.272305941765808, 56.710285549938135 ], [ 23.215823601577938, 56.711060696294055 ], [ 23.193654412920807, 56.704962877334879 ], [ 23.171743604883432, 56.692405504210626 ], [ 23.123322788415919, 56.698813382431638 ], [ 23.093372487350393, 56.720937509658313 ], [ 23.070130578363489, 56.760269970882405 ], [ 23.055827865763376, 56.794238914993912 ], [ 23.036161634251982, 56.80675378885627 ], [ 22.975375102778742, 56.801390271743628 ], [ 22.932466963179763, 56.810329467231099 ], [ 22.923527767692292, 56.833571376218003 ], [ 22.943193998304366, 56.855025446467153 ], [ 22.923527767692292, 56.874691677079227 ], [ 22.918164249680387, 56.888994390578659 ], [ 22.916376410942576, 56.915811977940393 ], [ 22.873468270444334, 56.924751173427865 ], [ 22.830560130845356, 56.9497809220519 ], [ 22.818045256982998, 56.974810670675879 ], [ 22.830560130845356, 57.006991775150254 ], [ 22.862741235319731, 57.010567453525141 ], [ 22.878831788456239, 57.019506649911932 ], [ 22.866316913694618, 57.037385040886875 ], [ 22.855589879469335, 57.057051271498949 ], [ 22.841287165969902, 57.078505341748098 ], [ 22.896710179431238, 57.089232376872644 ], [ 22.912800732567746, 57.103535090372077 ], [ 22.941406158667291, 57.11426212549668 ], [ 22.955708872166724, 57.126776999358981 ], [ 22.930931431040676, 57.144574286773889 ], [ 22.941473422560364, 57.152454942441636 ], [ 22.946744419219556, 57.160335598109327 ], [ 22.948914828656427, 57.167880356992839 ], [ 22.967259963276661, 57.183538316440149 ], [ 22.977491896433833, 57.187052313913114 ], [ 23.039865350005869, 57.210203356299814 ], [ 23.080379673283289, 57.238470363916122 ], [ 23.112780795996912, 57.265290432507413 ], [ 23.129129271098975, 57.242986545192878 ], [ 23.132704949473805, 57.209017601081371 ], [ 23.120190075611504, 57.171472978595034 ], [ 23.10052384410011, 57.155382425458527 ], [ 23.113038718861787, 57.137504034483584 ], [ 23.150583341348124, 57.123201320984151 ], [ 23.204218516071649, 57.105322929109889 ], [ 23.231036103433382, 57.06599046788574 ], [ 23.21315771155912, 57.039172880524006 ], [ 23.177400928709858, 57.012355293162216 ], [ 23.168461732323067, 56.985537705800482 ], [ 23.197067159321932, 56.967659313926163 ], [ 23.177400928709858, 56.955144439164485 ], [ 23.141644144961276, 56.935478208552468 ], [ 23.148795501710993, 56.921175495952355 ], [ 23.200642837696762, 56.930114691439826 ], [ 23.277519921407247, 56.915811977940393 ], [ 23.372275396991938, 56.910448460827752 ], [ 23.442001124852027, 56.926539013064996 ], [ 23.477151320377118, 56.936473294007271 ], [ 23.492034132569131, 56.934638780455316 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-026", "NAME_1": "Dobele" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.397207879440089, 56.792244371580466 ], [ 23.408835076577532, 56.792399400312036 ], [ 23.451674838922656, 56.799401556836358 ], [ 23.48660810717837, 56.815912177005316 ], [ 23.50552168078093, 56.808212389390235 ], [ 23.525262079381434, 56.763667303803004 ], [ 23.502886183350654, 56.746872463693194 ], [ 23.459271274649609, 56.734573472987336 ], [ 23.436481968368128, 56.723876450937439 ], [ 23.417413364235358, 56.706823229308554 ], [ 23.442683140114809, 56.696798000827073 ], [ 23.462991977697584, 56.692534695419852 ], [ 23.478236525095554, 56.684809068483673 ], [ 23.481492140150124, 56.680416571867283 ], [ 23.479786817807394, 56.676773383185036 ], [ 23.484282668110609, 56.664913642050919 ], [ 23.485367872829045, 56.660288601437799 ], [ 23.476066114759362, 56.656180324762147 ], [ 23.444543491189165, 56.641555894988528 ], [ 23.438187289811538, 56.634450384777381 ], [ 23.424699740700476, 56.622125556549179 ], [ 23.412917514831406, 56.616027736690683 ], [ 23.405941195829485, 56.606441759579525 ], [ 23.480251905800799, 56.599542954943388 ], [ 23.477151320377118, 56.586933905875014 ], [ 23.448419223868086, 56.579079087729667 ], [ 23.436792025831323, 56.577063707024422 ], [ 23.432141147695802, 56.57083669685602 ], [ 23.434466586763563, 56.564092922750149 ], [ 23.445938755169379, 56.556393134235748 ], [ 23.458651157025258, 56.544817613042369 ], [ 23.459271274649609, 56.529831448062851 ], [ 23.44748904788122, 56.505621039379434 ], [ 23.437877232348399, 56.499264838001864 ], [ 23.415087925167597, 56.493296210251856 ], [ 23.393729467241087, 56.518911683727197 ], [ 23.383002432116541, 56.545729271088987 ], [ 23.35618484475475, 56.545729271088987 ], [ 23.315064543893584, 56.513548166614555 ], [ 23.273944243032361, 56.490306257627651 ], [ 23.288246956531793, 56.468852187378502 ], [ 23.309701026780942, 56.459912991891031 ], [ 23.275732082669435, 56.454549473879069 ], [ 23.214945551196195, 56.454549473879069 ], [ 23.172037410697953, 56.45097379550424 ], [ 23.148795501710993, 56.43309540362992 ], [ 23.129129271098975, 56.445610278391598 ], [ 23.121977914349259, 56.470640026116257 ], [ 23.089796809874883, 56.479579222503105 ], [ 23.086221131499997, 56.524275201739158 ], [ 23.088008970237752, 56.545729271088987 ], [ 23.066554899988603, 56.551092789100892 ], [ 23.041525152263944, 56.577910376462683 ], [ 23.021858921651869, 56.59042525032504 ], [ 22.980738620790703, 56.577910376462683 ], [ 22.961072389279309, 56.601152285449587 ], [ 22.925315606430104, 56.588637411587229 ], [ 22.930679123542689, 56.568971180075891 ], [ 22.896710179431238, 56.56718334133808 ], [ 22.862741235319731, 56.552880627838704 ], [ 22.848438522719619, 56.538577914339271 ], [ 22.834135809220186, 56.540365753976346 ], [ 22.82340877409564, 56.561819824225495 ], [ 22.798123000176304, 56.56833038973565 ], [ 22.791921828429622, 56.578975734942162 ], [ 22.788614535632291, 56.581352850853364 ], [ 22.784428744590855, 56.585771185891474 ], [ 22.778692660837635, 56.58781240501844 ], [ 22.776987339394168, 56.591791490484866 ], [ 22.776677280132333, 56.596649075094717 ], [ 22.776987339394168, 56.601351630073623 ], [ 22.780863071173769, 56.606105861895969 ], [ 22.793162061879627, 56.60729442030123 ], [ 22.817191603409753, 56.607346096245294 ], [ 22.829283887641282, 56.610498359411736 ], [ 22.840756056047155, 56.617190457573543 ], [ 22.854295282001658, 56.630858872938632 ], [ 22.859256219399015, 56.644243069262188 ], [ 22.837810500254363, 56.658454087885843 ], [ 22.828663770916251, 56.671579901791006 ], [ 22.805254347010475, 56.678375352740318 ], [ 22.800138380881549, 56.696022854471153 ], [ 22.826028272586711, 56.698089912019782 ], [ 22.875224237208784, 56.70904531468949 ], [ 22.90343956888097, 56.711629137074965 ], [ 22.986173536879221, 56.71075063703222 ], [ 23.028703240861887, 56.694937648853397 ], [ 23.083945346700375, 56.690648505024456 ], [ 23.099293246885793, 56.691578681011265 ], [ 23.123322788415919, 56.698813382431638 ], [ 23.171743604883432, 56.692405504210626 ], [ 23.193654412920807, 56.704962877334879 ], [ 23.215823601577938, 56.711060696294055 ], [ 23.272305941765808, 56.710285549938135 ], [ 23.299642775193888, 56.715349840122997 ], [ 23.325222609435855, 56.749456285179406 ], [ 23.314990676278683, 56.765475978933239 ], [ 23.315920851366172, 56.774157620277947 ], [ 23.321966994381285, 56.782115790311423 ], [ 23.375038689883638, 56.800770982394909 ], [ 23.389456414082247, 56.793975532344916 ], [ 23.397207879440089, 56.792244371580466 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-040", "NAME_1": "Jaunpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.083945346700375, 56.690648505024456 ], [ 23.028703240861887, 56.694937648853397 ], [ 22.986173536879221, 56.71075063703222 ], [ 22.90343956888097, 56.711629137074965 ], [ 22.875224237208784, 56.70904531468949 ], [ 22.826028272586711, 56.698089912019782 ], [ 22.818431837759078, 56.708218492389506 ], [ 22.814866164341993, 56.711060696294055 ], [ 22.814401076348588, 56.715246487335492 ], [ 22.818896925752483, 56.717830308821647 ], [ 22.824788039136706, 56.719794013582828 ], [ 22.828663770916251, 56.725969346907789 ], [ 22.826648390211005, 56.733746649788031 ], [ 22.82060224809527, 56.771547960370071 ], [ 22.787684359645482, 56.796791896928482 ], [ 22.788924594894127, 56.803871567818589 ], [ 22.816257417345923, 56.813905145605929 ], [ 22.846650683082544, 56.828207859105362 ], [ 22.835923647957998, 56.849661929354511 ], [ 22.875256110081409, 56.858601124841982 ], [ 22.907437214555785, 56.853237607729397 ], [ 22.943193998304366, 56.855025446467153 ], [ 22.923527767692292, 56.833571376218003 ], [ 22.932466963179763, 56.810329467231099 ], [ 22.975375102778742, 56.801390271743628 ], [ 23.036161634251982, 56.80675378885627 ], [ 23.055827865763376, 56.794238914993912 ], [ 23.070130578363489, 56.760269970882405 ], [ 23.093372487350393, 56.720937509658313 ], [ 23.123322788415919, 56.698813382431638 ], [ 23.099293246885793, 56.691578681011265 ], [ 23.083945346700375, 56.690648505024456 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-018", "NAME_1": "Brocenu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 22.788924594894127, 56.803871567818589 ], [ 22.787684359645482, 56.796791896928482 ], [ 22.82060224809527, 56.771547960370071 ], [ 22.826648390211005, 56.733746649788031 ], [ 22.828663770916251, 56.725969346907789 ], [ 22.824788039136706, 56.719794013582828 ], [ 22.818896925752483, 56.717830308821647 ], [ 22.814401076348588, 56.715246487335492 ], [ 22.814866164341993, 56.711060696294055 ], [ 22.818431837759078, 56.708218492389506 ], [ 22.826028272586711, 56.698089912019782 ], [ 22.800138380881549, 56.696022854471153 ], [ 22.805254347010475, 56.678375352740318 ], [ 22.828663770916251, 56.671579901791006 ], [ 22.837810500254363, 56.658454087885843 ], [ 22.859256219399015, 56.644243069262188 ], [ 22.854295282001658, 56.630858872938632 ], [ 22.840756056047155, 56.617190457573543 ], [ 22.829283887641282, 56.610498359411736 ], [ 22.817191603409753, 56.607346096245294 ], [ 22.793162061879627, 56.60729442030123 ], [ 22.780863071173769, 56.606105861895969 ], [ 22.776987339394168, 56.601351630073623 ], [ 22.776677280132333, 56.596649075094717 ], [ 22.776987339394168, 56.591791490484866 ], [ 22.778692660837635, 56.58781240501844 ], [ 22.784428744590855, 56.585771185891474 ], [ 22.788614535632291, 56.581352850853364 ], [ 22.791921828429622, 56.578975734942162 ], [ 22.798123000176304, 56.56833038973565 ], [ 22.773421665077763, 56.567503567435665 ], [ 22.755128208200233, 56.562025865201463 ], [ 22.735904575335894, 56.565539863573804 ], [ 22.732287225075368, 56.565281481155409 ], [ 22.712074313678613, 56.565372530317973 ], [ 22.693722422451799, 56.588226511344374 ], [ 22.654856041173332, 56.604230314977031 ], [ 22.60455837107196, 56.599657799268016 ], [ 22.574837020312202, 56.567650192002759 ], [ 22.549688186160836, 56.579081479926344 ], [ 22.535970639933055, 56.620234119509007 ], [ 22.501676773464283, 56.638524180546483 ], [ 22.497104258654588, 56.684249334938841 ], [ 22.487959227236558, 56.723115716217308 ], [ 22.460524134780997, 56.736833262445089 ], [ 22.442234072844144, 56.771127128913861 ], [ 22.462810392185816, 56.805420994483256 ], [ 22.490854932708942, 56.851517238829501 ], [ 22.527648552938331, 56.841595364034788 ], [ 22.586301304361712, 56.837151191474277 ], [ 22.634257032835762, 56.82412873035662 ], [ 22.645884229973205, 56.816351427476377 ], [ 22.642008498193604, 56.808212389390235 ], [ 22.679060499942125, 56.809555976527065 ], [ 22.718799675964249, 56.802011216744233 ], [ 22.788924594894127, 56.803871567818589 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-069", "NAME_1": "Ozolnieku" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.996344434647938, 56.730491034733461 ], [ 23.998204786621613, 56.721602687813743 ], [ 24.00812666231559, 56.709975491575676 ], [ 24.000685256219583, 56.695635280843533 ], [ 24.033913202132567, 56.68821971316919 ], [ 24.042129753685174, 56.661244614947009 ], [ 24.034533318857541, 56.659487617559478 ], [ 24.023474561601745, 56.658505763829908 ], [ 24.017738477848468, 56.662743231714728 ], [ 24.00890180777219, 56.666205553243685 ], [ 24.006731398335376, 56.664758613319293 ], [ 24.009987013389946, 56.658867499035807 ], [ 24.036393669931897, 56.641478379723424 ], [ 24.046160515994984, 56.637757677574712 ], [ 24.050966423761452, 56.635251370454341 ], [ 24.050346307036421, 56.631840724869505 ], [ 24.051896599748261, 56.627964993089904 ], [ 24.055152214802831, 56.623779202048524 ], [ 24.057477654769912, 56.619076647069562 ], [ 24.049106072687096, 56.610989284927541 ], [ 24.039339226624008, 56.608793037069006 ], [ 24.030037468554326, 56.608793037069006 ], [ 24.02647179513724, 56.607837023559739 ], [ 24.026575147924746, 56.602746894053894 ], [ 24.040889520235169, 56.592411607209897 ], [ 24.055617302796293, 56.58574534836913 ], [ 24.081558872244159, 56.581895454111873 ], [ 24.094271274999301, 56.581766262003384 ], [ 24.106673617593344, 56.580370998922433 ], [ 24.11023929190975, 56.574944973531672 ], [ 24.10527835541177, 56.5639637324403 ], [ 24.088380160715815, 56.550760403269976 ], [ 24.078871697970442, 56.535464179028622 ], [ 24.080938754619808, 56.527454332151706 ], [ 24.042904900940414, 56.502830512318269 ], [ 24.031742790897056, 56.501719469178113 ], [ 24.017743371059737, 56.502013034873642 ], [ 23.99231367413671, 56.502546292377474 ], [ 23.969413677499176, 56.518911683727197 ], [ 23.964050160386591, 56.554668467475778 ], [ 23.940808251399687, 56.579698215200438 ], [ 23.912202824400822, 56.608303642199303 ], [ 23.865719006427014, 56.615454998949019 ], [ 23.82817438394062, 56.627969872811377 ], [ 23.791941897500578, 56.646880719669468 ], [ 23.807818331051124, 56.65419577011869 ], [ 23.793615510647271, 56.681498590287674 ], [ 23.731209870339228, 56.690498589838 ], [ 23.710176999369025, 56.70663479615888 ], [ 23.683359411107915, 56.697695599772089 ], [ 23.670844537245557, 56.715573991646352 ], [ 23.701237802982178, 56.720937509658313 ], [ 23.701237802982178, 56.737028061895501 ], [ 23.756307407161103, 56.744133613475469 ], [ 23.767469517204461, 56.737932440829468 ], [ 23.783902622108315, 56.732093004288686 ], [ 23.801265902999035, 56.729535021224194 ], [ 23.811239454637132, 56.73255809228209 ], [ 23.818215772739677, 56.735503648074882 ], [ 23.828292678064599, 56.738139147303798 ], [ 23.900898064793864, 56.745838934918879 ], [ 23.912886997137207, 56.745425522869539 ], [ 23.919553256877293, 56.742144070292568 ], [ 23.924979282268055, 56.734108384993931 ], [ 23.935211216324547, 56.731240343566981 ], [ 23.941412388071228, 56.728579006815664 ], [ 23.996344434647938, 56.730491034733461 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-JEL", "NAME_1": "Jelgava" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 23.683804230181067, 56.674098589877985 ], [ 23.731209870339228, 56.690498589838 ], [ 23.793615510647271, 56.681498590287674 ], [ 23.807818331051124, 56.65419577011869 ], [ 23.791941897500578, 56.646880719669468 ], [ 23.769818331450836, 56.636687308972114 ], [ 23.778804230530795, 56.6175788486849 ], [ 23.739007050035298, 56.6076591070821 ], [ 23.707587308907137, 56.591887308752291 ], [ 23.670992949664878, 56.601501410361777 ], [ 23.676992949365115, 56.629895770073688 ], [ 23.631181668699014, 56.650298589508282 ], [ 23.617950646704969, 56.669543712572249 ], [ 23.644412690693116, 56.677963454250005 ], [ 23.683804230181067, 56.674098589877985 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-DGV", "NAME_1": "Daugavpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 26.594919269308832, 55.962130279488804 ], [ 26.609768905510577, 55.958673419542663 ], [ 26.612579916429979, 55.94602387130459 ], [ 26.61679643280911, 55.923535784848582 ], [ 26.61117441097025, 55.903858709311976 ], [ 26.631927930929237, 55.897406317347645 ], [ 26.6249292322982, 55.888861395437004 ], [ 26.616709831693356, 55.881170965267813 ], [ 26.601817253763215, 55.873724676752374 ], [ 26.597119357272504, 55.85326051546042 ], [ 26.581658797215709, 55.840610967222347 ], [ 26.556359700739563, 55.837799956302888 ], [ 26.532466108823826, 55.832177934464085 ], [ 26.51841105512608, 55.826555912625224 ], [ 26.504356000528958, 55.840610967222347 ], [ 26.490300946831212, 55.85326051546042 ], [ 26.48748993591181, 55.868721074617895 ], [ 26.485769076881354, 55.892889715188176 ], [ 26.449310742796513, 55.90570709850374 ], [ 26.437917514181891, 55.914618231546115 ], [ 26.471864215361222, 55.933230279553754 ], [ 26.54110825823949, 55.92708533342136 ], [ 26.594919269308832, 55.962130279488804 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-REZ", "NAME_1": "Rezekne" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 27.340169016386994, 56.560760606316876 ], [ 27.363711316923741, 56.555221273542884 ], [ 27.365536672679298, 56.538793073541456 ], [ 27.360973283290377, 56.520539517784471 ], [ 27.355497216923027, 56.494984539005259 ], [ 27.339069016921599, 56.47490562749266 ], [ 27.284639408082171, 56.489658775432133 ], [ 27.274284355038958, 56.515546408040279 ], [ 27.310056339128721, 56.553347928164328 ], [ 27.340169016386994, 56.560760606316876 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-028", "NAME_1": "Durbes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 21.292439258165189, 56.756756759416874 ], [ 21.31589130364074, 56.739700725607975 ], [ 21.337211346126708, 56.718380684021326 ], [ 21.399039466997806, 56.692796633757609 ], [ 21.403303475674818, 56.671476592170961 ], [ 21.431019529827438, 56.673608596059807 ], [ 21.452339571414086, 56.645892541907244 ], [ 21.490715646809633, 56.652288554473159 ], [ 21.518431701861516, 56.652288554473159 ], [ 21.571731806277796, 56.658684567039131 ], [ 21.607975876885178, 56.643760537119078 ], [ 21.618635898128161, 56.613912479077612 ], [ 21.618635898128161, 56.588328428813895 ], [ 21.571731806277796, 56.560612373762012 ], [ 21.52909172220518, 56.571272395004996 ], [ 21.499243664163771, 56.552084357307194 ], [ 21.52909172220518, 56.513708281911647 ], [ 21.482187630354815, 56.511576278022801 ], [ 21.473659613900054, 56.520104294477562 ], [ 21.433151533716284, 56.545688344741279 ], [ 21.399039466997806, 56.558480369873166 ], [ 21.407567483452567, 56.520104294477562 ], [ 21.354267379036287, 56.503048261567983 ], [ 21.347871366470372, 56.543556340852433 ], [ 21.256195186658545, 56.530764315720546 ], [ 21.254063182769642, 56.588328428813895 ], [ 21.281779236922205, 56.611780474289446 ], [ 21.258327190547391, 56.656552562250909 ], [ 21.288175249488177, 56.682136612514626 ], [ 21.273251220467444, 56.707720662778343 ], [ 21.260459195335557, 56.739700725607975 ], [ 21.292439258165189, 56.756756759416874 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-037", "NAME_1": "Incukalna" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.630122348067914, 57.061024109375126 ], [ 24.580062851719219, 57.059236269738051 ], [ 24.565530100633623, 57.060001151232143 ], [ 24.550455160876936, 57.083699004805226 ], [ 24.514297471950727, 57.079526964397871 ], [ 24.508734750508324, 57.105949890574948 ], [ 24.553236521148506, 57.124028735038053 ], [ 24.568534005340041, 57.137935538194483 ], [ 24.618598497422681, 57.132372816752081 ], [ 24.665881629233809, 57.125419415173837 ], [ 24.700648638024234, 57.137935538194483 ], [ 24.736806326950386, 57.147670300943616 ], [ 24.772964014977276, 57.147670300943616 ], [ 24.77991741745484, 57.135154177922971 ], [ 24.766010613399089, 57.115684653324081 ], [ 24.749322449971089, 57.098996488996761 ], [ 24.759057211820846, 57.078136284262087 ], [ 24.707602039602421, 57.062838800070551 ], [ 24.678397752254455, 57.060057439798982 ], [ 24.650584145042217, 57.073964242955412 ], [ 24.630122348067914, 57.061024109375126 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-011", "NAME_1": "Ādaži" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.34880651151127, 57.183827989869826 ], [ 24.372448077416834, 57.182437309734041 ], [ 24.394698962287237, 57.178265268427367 ], [ 24.425293929771044, 57.181046628698937 ], [ 24.467014340139656, 57.182437309734041 ], [ 24.508734750508324, 57.105949890574948 ], [ 24.501781348930081, 57.094824447690087 ], [ 24.458670258425684, 57.072573562819628 ], [ 24.407215086207259, 57.061448119934767 ], [ 24.362713314667701, 57.057276078628092 ], [ 24.320992904299089, 57.028071791280126 ], [ 24.290397936815339, 57.030853152451016 ], [ 24.261193650366636, 57.041978595335877 ], [ 24.247286846310885, 57.060057439798982 ], [ 24.258412289195746, 57.075354923091197 ], [ 24.26536569077399, 57.098996488996761 ], [ 24.320992904299089, 57.112903292153192 ], [ 24.337681068626409, 57.128200776344727 ], [ 24.341853109933083, 57.162967784235832 ], [ 24.34880651151127, 57.183827989869826 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-VMR", "NAME_1": "Valmiera" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.456935934545697, 57.537263956966001 ], [ 25.452238902303463, 57.515344472269533 ], [ 25.431885095021073, 57.505950407785065 ], [ 25.395874512698583, 57.509081762613221 ], [ 25.377086383729591, 57.509081762613221 ], [ 25.372389350588037, 57.527869892481533 ], [ 25.378652061143669, 57.549789377178001 ], [ 25.41153128773874, 57.556052086834313 ], [ 25.43971348209152, 57.556052086834313 ], [ 25.456935934545697, 57.537263956966001 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-022", "NAME_1": "Cesu" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.231478377924759, 57.296149626204283 ], [ 25.231478377924759, 57.330594530213375 ], [ 25.254963540035305, 57.325897497971141 ], [ 25.273751669903618, 57.318069110900694 ], [ 25.311327929640242, 57.31337207865846 ], [ 25.323853348952866, 57.308675045516907 ], [ 25.311327929640242, 57.296149626204283 ], [ 25.331681736922576, 57.291452593961992 ], [ 25.362995286103569, 57.296149626204283 ], [ 25.394308835284505, 57.302412335860595 ], [ 25.427188062778839, 57.289886916547914 ], [ 25.466329999030222, 57.277361496335971 ], [ 25.489815161140768, 57.263270399609212 ], [ 25.500774903938634, 57.24917930198319 ], [ 25.500774903938634, 57.216300075388119 ], [ 25.522694388635102, 57.20847168741841 ], [ 25.546179550745649, 57.213168719660644 ], [ 25.560270647472407, 57.222562785044488 ], [ 25.575927422512507, 57.203774655176176 ], [ 25.572796067684351, 57.170895428581161 ], [ 25.563402002300563, 57.147410266470615 ], [ 25.53208845311957, 57.156804330955083 ], [ 25.508603291009081, 57.156804330955083 ], [ 25.4960778716964, 57.152107298712849 ], [ 25.458501611959832, 57.156804330955083 ], [ 25.444410514333754, 57.170895428581161 ], [ 25.41153128773874, 57.186552202721941 ], [ 25.380217738557747, 57.188117880136019 ], [ 25.364560963517647, 57.205340332590254 ], [ 25.369257995759881, 57.219431430216275 ], [ 25.359863931275413, 57.231956849528956 ], [ 25.317590639296554, 57.231956849528956 ], [ 25.319156316710632, 57.258573366467658 ], [ 25.287842767529696, 57.280492851164126 ], [ 25.231478377924759, 57.296149626204283 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-055", "NAME_1": "Ligatnes" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 24.980969982678516, 57.238219559185268 ], [ 25.048294114181999, 57.241350914912744 ], [ 25.090567405261538, 57.247613624569112 ], [ 25.10465850288756, 57.233522526943034 ], [ 25.139103407796028, 57.233522526943034 ], [ 25.159457214179099, 57.227259817286722 ], [ 25.171982634391043, 57.206906010004332 ], [ 25.178245344047355, 57.181855170479707 ], [ 25.189205086845277, 57.163067040611395 ], [ 25.212690248955823, 57.145844589056537 ], [ 25.195467796501589, 57.133319168844537 ], [ 25.173548311805121, 57.11766239380438 ], [ 25.137537729482631, 57.111399684148068 ], [ 25.112486889958006, 57.10983400673399 ], [ 25.067082243150992, 57.114531038976224 ], [ 25.026374629485531, 57.119228071218458 ], [ 24.987446730082524, 57.124678860341078 ], [ 24.984036086296328, 57.140750230938352 ], [ 24.983570998302923, 57.145013536345573 ], [ 24.978248324800347, 57.158733629453423 ], [ 24.975664504213455, 57.170670884953324 ], [ 24.973132358671421, 57.177750555843431 ], [ 24.967396274918144, 57.185011094786148 ], [ 24.934375033680851, 57.20839468027026 ], [ 24.900371942311267, 57.218678290270873 ], [ 24.923471306955207, 57.236041572060856 ], [ 24.933134800230846, 57.246221829273964 ], [ 24.980969982678516, 57.238219559185268 ] ] ] } }, +{ "type": "Feature", "properties": { "ISO": "LV-JKB", "NAME_1": "Jekabpils" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 25.820736875106661, 56.486807300416558 ], [ 25.826426628486104, 56.527167059012527 ], [ 25.854129531852379, 56.535783197336684 ], [ 25.878617480312471, 56.535783197336684 ], [ 25.889748365894377, 56.522426134458499 ], [ 25.918688668946913, 56.529104666347223 ], [ 25.925367200835638, 56.513521426172872 ], [ 25.876556836728639, 56.495306707638406 ], [ 25.883069834904916, 56.475676414834652 ], [ 25.845224823566753, 56.477902592130874 ], [ 25.820736875106661, 56.486807300416558 ] ] ] } } +] +} diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts index 343865483172b..e690b1ef52bb9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts @@ -23,10 +23,13 @@ import { extractTimegrain, QueryFormData, } from '@superset-ui/core'; -import { BigNumberTotalChartProps } from '../types'; +import { BigNumberTotalChartProps, BigNumberVizProps } from '../types'; import { getDateFormatter, parseMetricValue } from '../utils'; +import { Refs } from '../../types'; -export default function transformProps(chartProps: BigNumberTotalChartProps) { +export default function transformProps( + chartProps: BigNumberTotalChartProps, +): BigNumberVizProps { const { width, height, queriesData, formData, rawFormData, hooks } = chartProps; const { @@ -38,6 +41,7 @@ export default function transformProps(chartProps: BigNumberTotalChartProps) { timeFormat, yAxisFormat, } = formData; + const refs: Refs = {}; const { data = [], coltypes = [] } = queriesData[0]; const granularity = extractTimegrain(rawFormData as QueryFormData); const metricName = getMetricLabel(metric); @@ -76,5 +80,6 @@ export default function transformProps(chartProps: BigNumberTotalChartProps) { subheaderFontSize, subheader: formattedSubheader, onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx index b7516561cd9b3..669926d58ba8e 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx @@ -20,17 +20,14 @@ import React, { MouseEvent } from 'react'; import { t, getNumberFormatter, - NumberFormatter, smartDateVerboseFormatter, - TimeFormatter, computeMaxFontSize, BRAND_COLOR, styled, BinaryQueryObjectFilterClause, } from '@superset-ui/core'; -import { EChartsCoreOption } from 'echarts'; import Echart from '../components/Echart'; -import { BigNumberWithTrendlineFormData, TimeSeriesDatum } from './types'; +import { BigNumberVizProps } from './types'; import { EventHandlers } from '../types'; const defaultNumberFormatter = getNumberFormatter(); @@ -44,36 +41,7 @@ const PROPORTION = { TRENDLINE: 0.3, }; -type BigNumberVisProps = { - className?: string; - width: number; - height: number; - bigNumber?: number | null; - bigNumberFallback?: TimeSeriesDatum; - headerFormatter: NumberFormatter | TimeFormatter; - formatTime: TimeFormatter; - headerFontSize: number; - kickerFontSize: number; - subheader: string; - subheaderFontSize: number; - showTimestamp?: boolean; - showTrendLine?: boolean; - startYAxisAtZero?: boolean; - timeRangeFixed?: boolean; - timestamp?: number; - trendLineData?: TimeSeriesDatum[]; - mainColor: string; - echartOptions: EChartsCoreOption; - onContextMenu?: ( - clientX: number, - clientY: number, - filters?: BinaryQueryObjectFilterClause[], - ) => void; - xValueFormatter?: TimeFormatter; - formData?: BigNumberWithTrendlineFormData; -}; - -class BigNumberVis extends React.PureComponent { +class BigNumberVis extends React.PureComponent { static defaultProps = { className: '', headerFormatter: defaultNumberFormatter, @@ -108,7 +76,7 @@ class BigNumberVis extends React.PureComponent { renderFallbackWarning() { const { bigNumberFallback, formatTime, showTimestamp } = this.props; - if (!bigNumberFallback || showTimestamp) return null; + if (!formatTime || !bigNumberFallback || showTimestamp) return null; return ( { renderKicker(maxHeight: number) { const { timestamp, showTimestamp, formatTime, width } = this.props; - if (!showTimestamp) return null; + if ( + !formatTime || + !showTimestamp || + typeof timestamp === 'string' || + typeof timestamp === 'boolean' + ) + return null; const text = timestamp === null ? '' : formatTime(timestamp); @@ -155,6 +129,7 @@ class BigNumberVis extends React.PureComponent { renderHeader(maxHeight: number) { const { bigNumber, headerFormatter, width } = this.props; + // @ts-ignore const text = bigNumber === null ? t('No data') : headerFormatter(bigNumber); const container = this.createTemporaryContainer(); @@ -231,7 +206,7 @@ class BigNumberVis extends React.PureComponent { } renderTrendline(maxHeight: number) { - const { width, trendLineData, echartOptions } = this.props; + const { width, trendLineData, echartOptions, refs } = this.props; // if can't find any non-null values, no point rendering the trendline if (!trendLineData?.some(d => d[1] !== null)) { @@ -264,12 +239,15 @@ class BigNumberVis extends React.PureComponent { }; return ( - + echartOptions && ( + + ) ); } @@ -292,7 +270,9 @@ class BigNumberVis extends React.PureComponent {
{this.renderFallbackWarning()} {this.renderKicker( - Math.ceil(kickerFontSize * (1 - PROPORTION.TRENDLINE) * height), + Math.ceil( + (kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height, + ), )} {this.renderHeader( Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height), @@ -311,7 +291,7 @@ class BigNumberVis extends React.PureComponent { return (
{this.renderFallbackWarning()} - {this.renderKicker(kickerFontSize * height)} + {this.renderKicker((kickerFontSize || 0) * height)} {this.renderHeader(Math.ceil(headerFontSize * height))} {this.renderSubheader(Math.ceil(subheaderFontSize * height))}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts index 2d8cc129c0bf4..bd4553479e487 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts @@ -30,11 +30,14 @@ import { } from '@superset-ui/core'; import { EChartsCoreOption, graphic } from 'echarts'; import { + BigNumberVizProps, BigNumberDatum, BigNumberWithTrendlineChartProps, TimeSeriesDatum, } from '../types'; import { getDateFormatter, parseMetricValue } from '../utils'; +import { getDefaultTooltip } from '../../utils/tooltip'; +import { Refs } from '../../types'; const defaultNumberFormatter = getNumberFormatter(); export function renderTooltipFactory( @@ -60,7 +63,7 @@ const formatPercentChange = getNumberFormatter( export default function transformProps( chartProps: BigNumberWithTrendlineChartProps, -) { +): BigNumberVizProps { const { width, height, @@ -95,6 +98,7 @@ export default function transformProps( from_dttm: fromDatetime, to_dttm: toDatetime, } = queriesData[0]; + const refs: Refs = {}; const metricName = getMetricLabel(metric); const compareLag = Number(compareLag_) || 0; let formattedSubheader = subheader; @@ -103,7 +107,7 @@ export default function transformProps( const mainColor = `rgb(${r}, ${g}, ${b})`; const xAxisLabel = getXAxisLabel(rawFormData) as string; - let trendLineData; + let trendLineData: TimeSeriesDatum[] | undefined; let percentChange = 0; let bigNumber = data.length === 0 ? null : data[0][metricName]; let timestamp = data.length === 0 ? null : data[0][xAxisLabel]; @@ -144,6 +148,7 @@ export default function transformProps( } } sortedData.reverse(); + // @ts-ignore trendLineData = showTrendLine ? sortedData : undefined; } @@ -229,10 +234,9 @@ export default function transformProps( bottom: 0, }, tooltip: { - appendToBody: true, + ...getDefaultTooltip(refs), show: !inContextMenu, trigger: 'axis', - confine: true, formatter: renderTooltipFactory(formatTime, headerFormatter), }, aria: { @@ -250,6 +254,7 @@ export default function transformProps( width, height, bigNumber, + // @ts-ignore bigNumberFallback, className, headerFormatter, @@ -267,5 +272,6 @@ export default function transformProps( echartOptions, onContextMenu, xValueFormatter: formatTime, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts index 60c43770e37c4..90b852b01e4e8 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts @@ -17,12 +17,17 @@ * under the License. */ +import { EChartsCoreOption } from 'echarts'; import { + BinaryQueryObjectFilterClause, ChartDataResponseResult, - ChartProps, + DataRecordValue, + NumberFormatter, QueryFormData, QueryFormMetric, + TimeFormatter, } from '@superset-ui/core'; +import { BaseChartProps, Refs } from '../types'; export interface BigNumberDatum { [key: string]: number | null; @@ -43,15 +48,50 @@ export type BigNumberWithTrendlineFormData = BigNumberTotalFormData & { compareLag?: string | number; }; -export type BigNumberTotalChartProps = ChartProps & { - formData: BigNumberTotalFormData; - queriesData: (ChartDataResponseResult & { - data?: BigNumberDatum[]; - })[]; -}; +export interface BigNumberTotalChartDataResponseResult + extends ChartDataResponseResult { + data: BigNumberDatum[]; +} -export type BigNumberWithTrendlineChartProps = BigNumberTotalChartProps & { - formData: BigNumberWithTrendlineFormData; -}; +export type BigNumberTotalChartProps = + BaseChartProps & { + formData: BigNumberTotalFormData; + queriesData: BigNumberTotalChartDataResponseResult[]; + }; + +export type BigNumberWithTrendlineChartProps = + BaseChartProps & { + formData: BigNumberWithTrendlineFormData; + }; export type TimeSeriesDatum = [number, number | null]; + +export type BigNumberVizProps = { + className?: string; + width: number; + height: number; + bigNumber?: DataRecordValue; + bigNumberFallback?: TimeSeriesDatum; + headerFormatter: NumberFormatter | TimeFormatter; + formatTime?: TimeFormatter; + headerFontSize: number; + kickerFontSize?: number; + subheader: string; + subheaderFontSize: number; + showTimestamp?: boolean; + showTrendLine?: boolean; + startYAxisAtZero?: boolean; + timeRangeFixed?: boolean; + timestamp?: DataRecordValue; + trendLineData?: TimeSeriesDatum[]; + mainColor?: string; + echartOptions?: EChartsCoreOption; + onContextMenu?: ( + clientX: number, + clientY: number, + filters?: BinaryQueryObjectFilterClause[], + ) => void; + xValueFormatter?: TimeFormatter; + formData?: BigNumberWithTrendlineFormData; + refs: Refs; +}; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/EchartsBoxPlot.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/EchartsBoxPlot.tsx index 29c4e2a6e62a9..135d31317e227 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/EchartsBoxPlot.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/EchartsBoxPlot.tsx @@ -31,6 +31,7 @@ export default function EchartsBoxPlot(props: BoxPlotChartTransformedProps) { groupby, selectedValues, formData, + refs, } = props; const handleChange = useCallback( (values: string[]) => { @@ -72,6 +73,7 @@ export default function EchartsBoxPlot(props: BoxPlotChartTransformedProps) { return ( [name, val]), tooltip: { + ...getDefaultTooltip(refs), formatter: (param: { data: [string, number] }) => { const [outlierName, stats] = param.data; const headline = groupbyLabels.length @@ -194,6 +198,7 @@ export default function transformProps( type: 'boxplot', data: transformedData, tooltip: { + ...getDefaultTooltip(refs), formatter: (param: CallbackDataParams) => { // @ts-ignore const { @@ -270,7 +275,7 @@ export default function transformProps( nameLocation: yAxisTitlePosition === 'Left' ? 'middle' : 'end', }, tooltip: { - ...defaultTooltip, + ...getDefaultTooltip(refs), show: !inContextMenu, trigger: 'item', axisPointer: { @@ -291,5 +296,6 @@ export default function transformProps( groupby, selectedValues, onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/types.ts index 5fac1ae216867..dcbc9da17aa9b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/types.ts @@ -16,12 +16,14 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryFormData } from '@superset-ui/core'; import { - ChartDataResponseResult, - ChartProps, - QueryFormData, -} from '@superset-ui/core'; -import { EchartsTitleFormData, EChartTransformedProps } from '../types'; + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, + TitleFormData, +} from '../types'; import { DEFAULT_TITLE_FORM_DATA } from '../constants'; export type BoxPlotQueryFormData = QueryFormData & { @@ -29,7 +31,7 @@ export type BoxPlotQueryFormData = QueryFormData & { whiskerOptions?: BoxPlotFormDataWhiskerOptions; xTickLayout?: BoxPlotFormXTickLayout; emitFilter: boolean; -} & EchartsTitleFormData; +} & TitleFormData; export type BoxPlotFormDataWhiskerOptions = | 'Tukey' @@ -51,10 +53,11 @@ export const DEFAULT_FORM_DATA: BoxPlotQueryFormData = { }; export interface EchartsBoxPlotChartProps - extends ChartProps { + extends BaseChartProps { formData: BoxPlotQueryFormData; - queriesData: ChartDataResponseResult[]; } export type BoxPlotChartTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + CrossFilterTransformedProps & + ContextMenuTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx index 52de923659ff9..c492500e8e9a7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx @@ -31,6 +31,7 @@ export default function EchartsFunnel(props: FunnelChartTransformedProps) { groupby, selectedValues, formData, + refs, } = props; const handleChange = useCallback( (values: string[]) => { @@ -72,6 +73,7 @@ export default function EchartsFunnel(props: FunnelChartTransformedProps) { return ( @@ -212,7 +215,7 @@ export default function transformProps( ...defaultGrid, }, tooltip: { - ...defaultTooltip, + ...getDefaultTooltip(refs), show: !inContextMenu, trigger: 'item', formatter: (params: any) => @@ -240,5 +243,6 @@ export default function transformProps( groupby, selectedValues, onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/types.ts index 0d1f3caf5e5ad..841722ce4bc5b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/types.ts @@ -16,21 +16,20 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryFormData } from '@superset-ui/core'; import { - ChartDataResponseResult, - ChartProps, - QueryFormData, -} from '@superset-ui/core'; -import { - EchartsLegendFormData, - EChartTransformedProps, + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, + LegendFormData, LegendOrientation, LegendType, } from '../types'; import { DEFAULT_LEGEND_FORM_DATA } from '../constants'; export type EchartsFunnelFormData = QueryFormData & - EchartsLegendFormData & { + LegendFormData & { colorScheme?: string; groupby: QueryFormData[]; labelLine: boolean; @@ -54,9 +53,8 @@ export enum EchartsFunnelLabelTypeType { } export interface EchartsFunnelChartProps - extends ChartProps { + extends BaseChartProps { formData: EchartsFunnelFormData; - queriesData: ChartDataResponseResult[]; } // @ts-ignore @@ -76,4 +74,6 @@ export const DEFAULT_FORM_DATA: EchartsFunnelFormData = { }; export type FunnelChartTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + CrossFilterTransformedProps & + ContextMenuTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx index 118783b47eb96..7ffb571b79481 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx @@ -31,6 +31,7 @@ export default function EchartsGauge(props: GaugeChartTransformedProps) { groupby, selectedValues, formData: { emitFilter }, + refs, } = props; const handleChange = useCallback( (values: string[]) => { @@ -72,6 +73,7 @@ export default function EchartsGauge(props: GaugeChartTransformedProps) { return ( { const { name, value } = params; return `${name} : ${formatValue(value as number)}`; @@ -300,6 +304,7 @@ export default function transformProps( axisTick, pointer, detail, + // @ts-ignore tooltip, radius: Math.min(width, height) / 2 - axisLabelDistance - axisTickDistance, @@ -310,7 +315,7 @@ export default function transformProps( const echartOptions: EChartsCoreOption = { tooltip: { - appendToBody: true, + ...getDefaultTooltip(refs), trigger: 'item', }, series, @@ -327,5 +332,6 @@ export default function transformProps( groupby, selectedValues: filterState.selectedValues || [], onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts index 4824d579c4040..9f2c08fd5f0b0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryFormColumn, QueryFormData } from '@superset-ui/core'; import { - ChartDataResponseResult, - ChartProps, - QueryFormColumn, - QueryFormData, -} from '@superset-ui/core'; -import { EChartTransformedProps } from '../types'; + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, +} from '../types'; import { DEFAULT_LEGEND_FORM_DATA } from '../constants'; export type AxisTickLineStyle = { @@ -80,10 +80,11 @@ export const DEFAULT_FORM_DATA: Partial = { }; export interface EchartsGaugeChartProps - extends ChartProps { + extends BaseChartProps { formData: EchartsGaugeFormData; - queriesData: ChartDataResponseResult[]; } export type GaugeChartTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/EchartsGraph.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/EchartsGraph.tsx index 0f09fe2386f27..4f83d1bcaf578 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/EchartsGraph.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/EchartsGraph.tsx @@ -34,6 +34,7 @@ export default function EchartsGraph({ echartOptions, formData, onContextMenu, + refs, }: GraphChartTransformedProps) { const eventHandlers: EventHandlers = { contextmenu: (e: Event) => { @@ -68,6 +69,7 @@ export default function EchartsGraph({ }; return ( ; @@ -158,7 +160,7 @@ function getCategoryName(columnName: string, name?: DataRecordValue) { } export default function transformProps( - chartProps: ChartProps, + chartProps: EchartsGraphChartProps, ): GraphChartTransformedProps { const { width, height, formData, queriesData, hooks, inContextMenu } = chartProps; @@ -190,6 +192,7 @@ export default function transformProps( sliceId, }: EchartsGraphFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData }; + const refs: Refs = {}; const metricLabel = getMetricLabel(metric); const colorFn = CategoricalColorNamespace.getScale(colorScheme as string); const nodes: { [name: string]: number } = {}; @@ -210,7 +213,10 @@ export default function transformProps( value: 0, category, select: DEFAULT_GRAPH_SERIES_OPTION.select, - tooltip: DEFAULT_GRAPH_SERIES_OPTION.tooltip, + tooltip: { + ...getDefaultTooltip(refs), + ...DEFAULT_GRAPH_SERIES_OPTION.tooltip, + }, }); } const node = echartNodes[nodes[name]]; @@ -298,6 +304,7 @@ export default function transformProps( animationDuration: DEFAULT_GRAPH_SERIES_OPTION.animationDuration, animationEasing: DEFAULT_GRAPH_SERIES_OPTION.animationEasing, tooltip: { + ...getDefaultTooltip(refs), show: !inContextMenu, formatter: (params: any): string => edgeFormatter( @@ -322,5 +329,6 @@ export default function transformProps( formData, echartOptions, onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/types.ts index 70a068c977d3c..95dc386eb238a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/types.ts @@ -16,16 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { - PlainObject, - QueryFormData, - BinaryQueryObjectFilterClause, -} from '@superset-ui/core'; +import { QueryFormData } from '@superset-ui/core'; import { GraphNodeItemOption } from 'echarts/types/src/chart/graph/GraphSeries'; import { SeriesTooltipOption } from 'echarts/types/src/util/types'; import { - EchartsLegendFormData, - EchartsProps, + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + LegendFormData, LegendOrientation, LegendType, } from '../types'; @@ -34,7 +32,7 @@ import { DEFAULT_LEGEND_FORM_DATA } from '../constants'; export type EdgeSymbol = 'none' | 'circle' | 'arrow'; export type EchartsGraphFormData = QueryFormData & - EchartsLegendFormData & { + LegendFormData & { source: string; target: string; sourceCategory?: string; @@ -85,11 +83,10 @@ export type tooltipFormatParams = { data: { [name: string]: string }; }; -export type GraphChartTransformedProps = EchartsProps & { - formData: PlainObject; - onContextMenu?: ( - clientX: number, - clientY: number, - filters?: BinaryQueryObjectFilterClause[], - ) => void; -}; +export interface EchartsGraphChartProps + extends BaseChartProps { + formData: EchartsGraphFormData; +} + +export type GraphChartTransformedProps = + BaseTransformedProps & ContextMenuTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx index 8a5421d21763a..1d1cd6547752d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx @@ -43,6 +43,7 @@ export default function EchartsMixedTimeseries({ onContextMenu, xValueFormatter, xAxis, + refs, }: EchartsMixedTimeseriesChartTransformedProps) { const isFirstQuery = useCallback( (seriesIndex: number) => seriesIndex < seriesBreakdown, @@ -61,7 +62,7 @@ export default function EchartsMixedTimeseries({ const currentGroupBy = isFirstQuery(seriesIndex) ? groupby : groupbyB; const currentLabelMap = isFirstQuery(seriesIndex) ? labelMap : labelMapB; const groupbyValues = values - .map(value => currentLabelMap[value]) + .map(value => currentLabelMap?.[value]) .filter(value => !!value); setDataMask({ @@ -100,7 +101,7 @@ export default function EchartsMixedTimeseries({ const eventHandlers: EventHandlers = { click: props => { const { seriesName, seriesIndex } = props; - const values: string[] = Object.values(selectedValues); + const values: string[] = Object.values(selectedValues || {}); if (values.includes(seriesName)) { handleChange( values.filter(v => v !== seriesName), @@ -162,6 +163,7 @@ export default function EchartsMixedTimeseries({ return ( { const xValue: number = richTooltip @@ -513,5 +518,6 @@ export default function transformProps( label: xAxisLabel, type: xAxisType, }, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts index a78429a786ab6..a39e556cac6f9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts @@ -20,19 +20,20 @@ import { AnnotationLayer, TimeGranularity, QueryFormData, - ChartProps, - ChartDataResponseResult, QueryFormColumn, ContributionType, TimeFormatter, AxisType, } from '@superset-ui/core'; import { - EchartsLegendFormData, - EchartsTitleFormData, - StackType, + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, EchartsTimeseriesSeriesType, - EChartTransformedProps, + LegendFormData, + StackType, + TitleFormData, } from '../types'; import { DEFAULT_LEGEND_FORM_DATA, @@ -86,8 +87,8 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & { groupby: QueryFormColumn[]; groupbyB: QueryFormColumn[]; emitFilter: boolean; -} & EchartsLegendFormData & - EchartsTitleFormData; +} & LegendFormData & + TitleFormData; // @ts-ignore export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = { @@ -133,20 +134,22 @@ export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = { ...DEFAULT_TITLE_FORM_DATA, }; -export interface EchartsMixedTimeseriesProps extends ChartProps { +export interface EchartsMixedTimeseriesProps + extends BaseChartProps { formData: EchartsMixedTimeseriesFormData; - queriesData: ChartDataResponseResult[]; } export type EchartsMixedTimeseriesChartTransformedProps = - EChartTransformedProps & { - emitFilterB: boolean; - groupbyB: QueryFormColumn[]; - labelMapB: Record; - seriesBreakdown: number; - xValueFormatter: TimeFormatter | StringConstructor; - xAxis: { - label: string; - type: AxisType; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps & { + emitFilterB: boolean; + groupbyB: QueryFormColumn[]; + labelMapB: Record; + seriesBreakdown: number; + xValueFormatter: TimeFormatter | StringConstructor; + xAxis: { + label: string; + type: AxisType; + }; }; - }; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx index 37606ac6f8764..6de4c8423dc49 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx @@ -31,6 +31,7 @@ export default function EchartsPie(props: PieChartTransformedProps) { groupby, selectedValues, formData, + refs, } = props; const handleChange = useCallback( (values: string[]) => { @@ -72,6 +73,7 @@ export default function EchartsPie(props: PieChartTransformedProps) { return ( formatPieLabel({ @@ -341,5 +344,6 @@ export default function transformProps( groupby, selectedValues, onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts index e31127f7b39f3..4b45751a23cb0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts @@ -16,22 +16,20 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryFormColumn, QueryFormData } from '@superset-ui/core'; import { - ChartDataResponseResult, - ChartProps, - QueryFormColumn, - QueryFormData, -} from '@superset-ui/core'; -import { - EchartsLegendFormData, - EChartTransformedProps, + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, + LegendFormData, LegendOrientation, LegendType, } from '../types'; import { DEFAULT_LEGEND_FORM_DATA } from '../constants'; export type EchartsPieFormData = QueryFormData & - EchartsLegendFormData & { + LegendFormData & { colorScheme?: string; currentOwnValue?: string[] | null; donut: boolean; @@ -59,9 +57,9 @@ export enum EchartsPieLabelType { KeyValuePercent = 'key_value_percent', } -export interface EchartsPieChartProps extends ChartProps { +export interface EchartsPieChartProps + extends BaseChartProps { formData: EchartsPieFormData; - queriesData: ChartDataResponseResult[]; } // @ts-ignore @@ -84,4 +82,6 @@ export const DEFAULT_FORM_DATA: EchartsPieFormData = { }; export type PieChartTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Radar/EchartsRadar.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Radar/EchartsRadar.tsx index bcba60f5cbbae..1291b69c12404 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Radar/EchartsRadar.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Radar/EchartsRadar.tsx @@ -31,6 +31,7 @@ export default function EchartsRadar(props: RadarChartTransformedProps) { groupby, selectedValues, formData, + refs, } = props; const handleChange = useCallback( (values: string[]) => { @@ -72,6 +73,7 @@ export default function EchartsRadar(props: RadarChartTransformedProps) { return ( ; export type EchartsRadarFormData = QueryFormData & - EchartsLegendFormData & { + LegendFormData & { colorScheme?: string; columnConfig?: RadarColumnConfig; currentOwnValue?: string[] | null; @@ -57,9 +58,9 @@ export enum EchartsRadarLabelType { KeyValue = 'key_value', } -export interface EchartsRadarChartProps extends ChartProps { +export interface EchartsRadarChartProps + extends BaseChartProps { formData: EchartsRadarFormData; - queriesData: ChartDataResponseResult[]; } // @ts-ignore @@ -78,4 +79,6 @@ export const DEFAULT_FORM_DATA: EchartsRadarFormData = { }; export type RadarChartTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx index a178e5d05dd41..2ffdb0e87bdc9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx @@ -48,9 +48,12 @@ export default function EchartsTimeseries({ onContextMenu, xValueFormatter, xAxis, + refs, }: TimeseriesChartTransformedProps) { const { emitFilter, stack } = formData; const echartRef = useRef(null); + // eslint-disable-next-line no-param-reassign + refs.echartRef = echartRef; const lastTimeRef = useRef(Date.now()); const lastSelectedLegend = useRef(''); const clickTimer = useRef>(); @@ -256,7 +259,7 @@ export default function EchartsTimeseries({
Boolean(controls?.limit.value), - mapStateToProps: (state, controlState) => { - const timeserieslimitProps = - sharedControls.timeseries_limit_metric.mapStateToProps?.( - state, - controlState, - ) || {}; - timeserieslimitProps.value = state.controls?.limit?.value - ? controlState?.value - : []; - return timeserieslimitProps; - }, - }, - order_desc: { - label: t('Series Limit Sort Descending'), - default: false, - description: t( - 'Whether to sort descending or ascending if a series limit is present', - ), - }, - }, formDataOverrides: formData => ({ ...formData, metrics: getStandardizedControls().popAllMetrics(), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts index 0ddb1f53fe8d4..ad021f92b9189 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts @@ -36,6 +36,7 @@ import { prophetOperator, timeComparePivotOperator, flattenOperator, + sortOperator, } from '@superset-ui/chart-controls'; export default function buildQuery(formData: QueryFormData) { @@ -95,6 +96,7 @@ export default function buildQuery(formData: QueryFormData) { resampleOperator(formData, baseQueryObject), renameOperator(formData, baseQueryObject), contributionOperator(formData, baseQueryObject), + sortOperator(formData, baseQueryObject), flattenOperator(formData, baseQueryObject), // todo: move prophet before flatten prophetOperator(formData, baseQueryObject), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 8934bffda181c..fa947b421b9fe 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -44,7 +44,7 @@ import { OrientationType, } from './types'; import { DEFAULT_FORM_DATA } from './constants'; -import { ForecastSeriesEnum, ForecastValue } from '../types'; +import { ForecastSeriesEnum, ForecastValue, Refs } from '../types'; import { parseYAxisBound } from '../utils/controls'; import { currentSeries, @@ -68,7 +68,7 @@ import { rebaseForecastDatum, } from '../utils/forecast'; import { convertInteger } from '../utils/convertInteger'; -import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults'; +import { defaultGrid, defaultYAxis } from '../defaults'; import { getPadding, getTooltipTimeFormatter, @@ -84,6 +84,7 @@ import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP, } from '../constants'; +import { getDefaultTooltip } from '../utils/tooltip'; export default function transformProps( chartProps: EchartsTimeseriesChartProps, @@ -147,6 +148,7 @@ export default function transformProps( timeGrainSqla, orientation, }: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData }; + const refs: Refs = {}; const colorScale = CategoricalColorNamespace.getScale(colorScheme as string); const rebasedData = rebaseForecastDatum(data, verboseMap); @@ -379,9 +381,8 @@ export default function transformProps( xAxis, yAxis, tooltip: { + ...getDefaultTooltip(refs), show: !inContextMenu, - ...defaultTooltip, - appendToBody: true, trigger: richTooltip ? 'axis' : 'item', formatter: (params: any) => { const [xIndex, yIndex] = isHorizontal ? [1, 0] : [0, 1]; @@ -463,5 +464,6 @@ export default function transformProps( label: xAxisLabel, type: xAxisType, }, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts index f8545307efb38..82a204e38d7f9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts @@ -16,22 +16,24 @@ * specific language governing permissions and limitations * under the License. */ +import { OptionName } from 'echarts/types/src/util/types'; import { AnnotationLayer, - ChartDataResponseResult, - ChartProps, + AxisType, + ContributionType, QueryFormColumn, QueryFormData, - TimeGranularity, - ContributionType, TimeFormatter, - AxisType, + TimeGranularity, } from '@superset-ui/core'; import { - EchartsLegendFormData, - EChartTransformedProps, - EchartsTitleFormData, + BaseChartProps, + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, + LegendFormData, StackType, + TitleFormData, } from '../types'; export enum OrientationType { @@ -85,20 +87,22 @@ export type EchartsTimeseriesFormData = QueryFormData & { showExtraControls: boolean; percentageThreshold: number; orientation?: OrientationType; -} & EchartsLegendFormData & - EchartsTitleFormData; +} & LegendFormData & + TitleFormData; export interface EchartsTimeseriesChartProps - extends ChartProps { + extends BaseChartProps { formData: EchartsTimeseriesFormData; - queriesData: ChartDataResponseResult[]; } export type TimeseriesChartTransformedProps = - EChartTransformedProps & { - xValueFormatter: TimeFormatter | StringConstructor; - xAxis: { - label: string; - type: AxisType; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps & { + legendData?: OptionName[]; + xValueFormatter: TimeFormatter | StringConstructor; + xAxis: { + label: string; + type: AxisType; + }; }; - }; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/EchartsTree.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/EchartsTree.tsx index 9b42c6e553576..b1b3f8e896e04 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/EchartsTree.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/EchartsTree.tsx @@ -21,9 +21,17 @@ import { EchartsProps } from '../types'; import Echart from '../components/Echart'; export default function EchartsGraph({ + echartOptions, height, + refs, width, - echartOptions, }: EchartsProps) { - return ; + return ( + + ); } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/constants.ts index 463835966cc71..35567c3fc5939 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/constants.ts @@ -17,6 +17,7 @@ * under the License. */ import { TreeSeriesOption } from 'echarts'; +import { EchartsTreeFormData } from './types'; export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = { label: { @@ -28,3 +29,18 @@ export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = { animationEasing: 'cubicOut', lineStyle: { color: 'source', width: 1.5 }, }; + +export const DEFAULT_FORM_DATA: Partial = { + id: '', + parent: '', + name: '', + rootNodeId: '', + layout: 'orthogonal', + orient: 'LR', + symbol: 'emptyCircle', + symbolSize: 7, + roam: true, + nodeLabelPosition: 'left', + childLabelPosition: 'bottom', + emphasis: 'descendant', +}; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx index 79e0639277a40..f8035203fe6b3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx @@ -24,7 +24,7 @@ import { sections, sharedControls, } from '@superset-ui/chart-controls'; -import { DEFAULT_FORM_DATA } from './types'; +import { DEFAULT_FORM_DATA } from './constants'; const requiredEntity = { ...sharedControls.entity, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/transformProps.ts index c89fbe8b37049..ca3a4a9341f8a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/transformProps.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, getMetricLabel, DataRecordValue } from '@superset-ui/core'; +import { getMetricLabel, DataRecordValue } from '@superset-ui/core'; import { EChartsCoreOption, TreeSeriesOption } from 'echarts'; import { TreeSeriesCallbackDataParams, @@ -24,12 +24,14 @@ import { } from 'echarts/types/src/chart/tree/TreeSeries'; import { OptionName } from 'echarts/types/src/util/types'; import { + EchartsTreeChartProps, EchartsTreeFormData, - DEFAULT_FORM_DATA as DEFAULT_GRAPH_FORM_DATA, TreeDataRecord, + TreeTransformedProps, } from './types'; -import { DEFAULT_TREE_SERIES_OPTION } from './constants'; -import { EchartsProps } from '../types'; +import { DEFAULT_FORM_DATA, DEFAULT_TREE_SERIES_OPTION } from './constants'; +import { Refs } from '../types'; +import { getDefaultTooltip } from '../utils/tooltip'; export function formatTooltip({ params, @@ -49,8 +51,11 @@ export function formatTooltip({ ].join(''); } -export default function transformProps(chartProps: ChartProps): EchartsProps { +export default function transformProps( + chartProps: EchartsTreeChartProps, +): TreeTransformedProps { const { width, height, formData, queriesData } = chartProps; + const refs: Refs = {}; const data: TreeDataRecord[] = queriesData[0].data || []; const { @@ -67,7 +72,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps { nodeLabelPosition, childLabelPosition, emphasis, - }: EchartsTreeFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData }; + }: EchartsTreeFormData = { ...DEFAULT_FORM_DATA, ...formData }; const metricLabel = getMetricLabel(metric); const nameColumn = name || id; @@ -201,6 +206,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps { animationEasing: DEFAULT_TREE_SERIES_OPTION.animationEasing, series, tooltip: { + ...getDefaultTooltip(refs), trigger: 'item', triggerOn: 'mousemove', formatter: (params: any) => @@ -212,8 +218,10 @@ export default function transformProps(chartProps: ChartProps): EchartsProps { }; return { + formData, width, height, echartOptions, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/types.ts index 81db2d59508fd..0fde0cde2a177 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/types.ts @@ -16,9 +16,12 @@ * specific language governing permissions and limitations * under the License. */ +import { OptionName } from 'echarts/types/src/util/types'; +import { ChartDataResponseResult, QueryFormData } from '@superset-ui/core'; import { TreeSeriesNodeItemOption } from 'echarts/types/src/chart/tree/TreeSeries'; +import { BaseChartProps, BaseTransformedProps } from '../types'; -export type EchartsTreeFormData = { +export type EchartsTreeFormData = QueryFormData & { id: string; parent: string; name: string; @@ -35,21 +38,18 @@ export type EchartsTreeFormData = { emphasis: 'none' | 'ancestor' | 'descendant'; }; -export const DEFAULT_FORM_DATA: EchartsTreeFormData = { - id: '', - parent: '', - name: '', - rootNodeId: '', - layout: 'orthogonal', - orient: 'LR', - symbol: 'emptyCircle', - symbolSize: 7, - roam: true, - nodeLabelPosition: 'left', - childLabelPosition: 'bottom', - emphasis: 'descendant', -}; +export interface TreeChartDataResponseResult extends ChartDataResponseResult { + data: TreeDataRecord[]; +} + +export interface EchartsTreeChartProps + extends BaseChartProps { + formData: EchartsTreeFormData; + queriesData: TreeChartDataResponseResult[]; +} -export type TreeDataRecord = Record & { - children: TreeSeriesNodeItemOption[]; +export type TreeDataRecord = Record & { + children?: TreeSeriesNodeItemOption[]; }; + +export type TreeTransformedProps = BaseTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx index 1ff112cedd65a..8559688939b06 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx @@ -28,15 +28,16 @@ import { extractTreePathInfo } from './constants'; import { TreemapTransformedProps } from './types'; export default function EchartsTreemap({ - height, - width, echartOptions, - setDataMask, - labelMap, - groupby, - selectedValues, formData, + groupby, + height, + labelMap, onContextMenu, + refs, + setDataMask, + selectedValues, + width, }: TreemapTransformedProps) { const handleChange = useCallback( (values: string[]) => { @@ -113,6 +114,7 @@ export default function EchartsTreemap({ return ( @@ -309,7 +310,7 @@ export default function transformProps( const echartOptions: EChartsCoreOption = { tooltip: { - ...defaultTooltip, + ...getDefaultTooltip(refs), show: !inContextMenu, trigger: 'item', formatter: (params: any) => @@ -332,5 +333,6 @@ export default function transformProps( groupby, selectedValues: filterState.selectedValues || [], onContextMenu, + refs, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/types.ts index 9120fb72f726d..c318b2ac2a366 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/types.ts @@ -24,7 +24,12 @@ import { QueryFormMetric, } from '@superset-ui/core'; import { CallbackDataParams } from 'echarts/types/src/util/types'; -import { EChartTransformedProps, LabelPositionEnum } from '../types'; +import { + BaseTransformedProps, + ContextMenuTransformedProps, + CrossFilterTransformedProps, + LabelPositionEnum, +} from '../types'; export type EchartsTreemapFormData = QueryFormData & { colorScheme?: string; @@ -73,4 +78,6 @@ export interface TreemapSeriesCallbackDataParams extends CallbackDataParams { } export type TreemapTransformedProps = - EChartTransformedProps; + BaseTransformedProps & + ContextMenuTransformedProps & + CrossFilterTransformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx index 0ef7704311c21..011d62abacec4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx @@ -42,10 +42,15 @@ function Echart( eventHandlers, zrEventHandlers, selectedValues = {}, + refs, }: EchartsProps, ref: React.Ref, ) { const divRef = useRef(null); + if (refs) { + // eslint-disable-next-line no-param-reassign + refs.divRef = divRef; + } const chartRef = useRef(); const currentSelection = useMemo( () => Object.keys(selectedValues) || [], @@ -106,6 +111,7 @@ function Echart( // did mount useEffect(() => { handleSizeChange({ width, height }); + return () => chartRef.current?.dispose(); }, []); useLayoutEffect(() => { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index ec956a9591764..1c20128b67c64 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -20,8 +20,8 @@ import { JsonValue, t, TimeGranularity } from '@superset-ui/core'; import { ReactNode } from 'react'; import { - EchartsLegendFormData, - EchartsTitleFormData, + LegendFormData, + TitleFormData, LabelPositionEnum, LegendOrientation, LegendType, @@ -91,14 +91,14 @@ export const TIMEGRAIN_TO_TIMESTAMP = { [TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12, }; -export const DEFAULT_LEGEND_FORM_DATA: EchartsLegendFormData = { +export const DEFAULT_LEGEND_FORM_DATA: LegendFormData = { legendMargin: null, legendOrientation: LegendOrientation.Top, legendType: LegendType.Scroll, showLegend: true, }; -export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = { +export const DEFAULT_TITLE_FORM_DATA: TitleFormData = { xAxisTitle: '', xAxisTitleMargin: 0, yAxisTitle: '', @@ -107,3 +107,10 @@ export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = { }; export { DEFAULT_FORM_DATA } from './Timeseries/constants'; + +// How far away from the mouse should the tooltip be +export const TOOLTIP_POINTER_MARGIN = 10; + +// If no satisfactory position can be found, how far away +// from the edge of the window should the tooltip be kept +export const TOOLTIP_OVERFLOW_MARGIN = 5; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts b/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts index c74e3d78a0434..d76de5b53db84 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts @@ -1,4 +1,6 @@ +import { CallbackDataParams } from 'echarts/types/src/util/types'; import { LegendOrientation } from './types'; +import { TOOLTIP_POINTER_MARGIN, TOOLTIP_OVERFLOW_MARGIN } from './constants'; /** * Licensed to the Apache Software Foundation (ASF) under one @@ -23,7 +25,59 @@ export const defaultGrid = { }; export const defaultTooltip = { - confine: true, + position: ( + canvasMousePos: [number, number], + params: CallbackDataParams, + tooltipDom: HTMLElement, + rect: any, + sizes: { contentSize: [number, number]; viewSize: [number, number] }, + ) => { + // algorithm copy-pasted from here: + // https://github.com/apache/echarts/issues/5004#issuecomment-559668309 + + // The chart canvas position + const canvasRect = tooltipDom.parentElement + ?.getElementsByTagName('canvas')[0] + .getBoundingClientRect(); + + // The mouse coordinates relative to the whole window + // The first parameter to the position function is the mouse position relative to the canvas + const mouseX = canvasMousePos[0] + (canvasRect?.x || 0); + const mouseY = canvasMousePos[1] + (canvasRect?.y || 0); + + // The width and height of the tooltip dom element + const tooltipWidth = sizes.contentSize[0]; + const tooltipHeight = sizes.contentSize[1]; + + // Start by placing the tooltip top and right relative to the mouse position + let xPos = mouseX + TOOLTIP_POINTER_MARGIN; + let yPos = mouseY - TOOLTIP_POINTER_MARGIN - tooltipHeight; + + // The tooltip is overflowing past the right edge of the window + if (xPos + tooltipWidth >= document.documentElement.clientWidth) { + // Attempt to place the tooltip to the left of the mouse position + xPos = mouseX - TOOLTIP_POINTER_MARGIN - tooltipWidth; + + // The tooltip is overflowing past the left edge of the window + if (xPos <= 0) + // Place the tooltip a fixed distance from the left edge of the window + xPos = TOOLTIP_OVERFLOW_MARGIN; + } + + // The tooltip is overflowing past the top edge of the window + if (yPos <= 0) { + // Attempt to place the tooltip to the bottom of the mouse position + yPos = mouseY + TOOLTIP_POINTER_MARGIN; + + // The tooltip is overflowing past the bottom edge of the window + if (yPos + tooltipHeight >= document.documentElement.clientHeight) + // Place the tooltip a fixed distance from the top edge of the window + yPos = TOOLTIP_OVERFLOW_MARGIN; + } + + // Return the position (converted back to a relative position on the canvas) + return [xPos - (canvasRect?.x || 0), yPos - (canvasRect?.y || 0)]; + }, }; export const defaultYAxis = { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts index 8c20543e78e84..57ed645839992 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts @@ -16,15 +16,17 @@ * specific language governing permissions and limitations * under the License. */ +import React, { RefObject } from 'react'; import { + BinaryQueryObjectFilterClause, + ChartDataResponseResult, + ChartProps, HandlerFunction, QueryFormColumn, - BinaryQueryObjectFilterClause, SetDataMaskHook, } from '@superset-ui/core'; import { EChartsCoreOption, ECharts } from 'echarts'; import { TooltipMarker } from 'echarts/types/src/util/format'; -import { OptionName } from 'echarts/types/src/util/types'; import { AreaChartExtraControlsValue } from './constants'; export type EchartsStylesProps = { @@ -32,6 +34,11 @@ export type EchartsStylesProps = { width: number; }; +export type Refs = { + echartRef?: React.Ref; + divRef?: RefObject; +}; + export interface EchartsProps { height: number; width: number; @@ -40,6 +47,7 @@ export interface EchartsProps { zrEventHandlers?: EventHandlers; selectedValues?: Record; forceClear?: boolean; + refs: Refs; } export interface EchartsHandler { @@ -78,7 +86,7 @@ export type ForecastValue = { forecastUpper?: number; }; -export type EchartsLegendFormData = { +export type LegendFormData = { legendMargin: number | null | string; legendOrientation: LegendOrientation; legendType: LegendType; @@ -103,26 +111,41 @@ export enum LabelPositionEnum { InsideBottomRight = 'insideBottomRight', } -export interface EChartTransformedProps { +export interface BaseChartProps extends ChartProps { + queriesData: ChartDataResponseResult[]; +} + +export interface BaseTransformedProps { + echartOptions: EChartsCoreOption; formData: F; height: number; + onContextMenu?: ( + clientX: number, + clientY: number, + filters?: BinaryQueryObjectFilterClause[], + ) => void; + refs: Refs; width: number; - echartOptions: EChartsCoreOption; +} + +export type CrossFilterTransformedProps = { emitFilter: boolean; - setDataMask: SetDataMaskHook; - setControlValue?: HandlerFunction; - labelMap: Record; groupby: QueryFormColumn[]; + labelMap: Record; + setControlValue?: HandlerFunction; + setDataMask: SetDataMaskHook; selectedValues: Record; - legendData?: OptionName[]; +}; + +export type ContextMenuTransformedProps = { onContextMenu?: ( clientX: number, clientY: number, filters?: BinaryQueryObjectFilterClause[], ) => void; -} +}; -export interface EchartsTitleFormData { +export interface TitleFormData { xAxisTitle: string; xAxisTitleMargin: number; yAxisTitle: string; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/eventHandlers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/eventHandlers.ts index d7c552edfcad2..0d26b92d08df4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/eventHandlers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/eventHandlers.ts @@ -17,7 +17,11 @@ * under the License. */ import { BinaryQueryObjectFilterClause } from '@superset-ui/core'; -import { EChartTransformedProps, EventHandlers } from '../types'; +import { + BaseTransformedProps, + CrossFilterTransformedProps, + EventHandlers, +} from '../types'; export type Event = { name: string; @@ -40,8 +44,9 @@ export const clickEventHandler = export const contextMenuEventHandler = ( - groupby: EChartTransformedProps['groupby'], - onContextMenu: EChartTransformedProps['onContextMenu'], + groupby: (BaseTransformedProps & + CrossFilterTransformedProps)['groupby'], + onContextMenu: BaseTransformedProps['onContextMenu'], labelMap: Record, ) => (e: Event) => { @@ -65,7 +70,7 @@ export const contextMenuEventHandler = }; export const allEventHandlers = ( - transformedProps: EChartTransformedProps, + transformedProps: BaseTransformedProps & CrossFilterTransformedProps, handleChange: (values: string[]) => void, ) => { const { groupby, selectedValues, onContextMenu, labelMap } = transformedProps; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/tooltip.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/tooltip.ts new file mode 100644 index 0000000000000..c1fd7ac0a3f7f --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/tooltip.ts @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CallbackDataParams } from 'echarts/types/src/util/types'; +import { TOOLTIP_OVERFLOW_MARGIN, TOOLTIP_POINTER_MARGIN } from '../constants'; +import { Refs } from '../types'; + +export function getDefaultTooltip(refs: Refs) { + return { + appendToBody: true, + position: ( + canvasMousePos: [number, number], + params: CallbackDataParams, + tooltipDom: HTMLDivElement | null, + rect: any, + sizes: { contentSize: [number, number]; viewSize: [number, number] }, + ) => { + // algorithm partially based on this snippet: + // https://github.com/apache/echarts/issues/5004#issuecomment-559668309 + + // The chart canvas position + const divRect = refs.divRef?.current?.getBoundingClientRect(); + + // The mouse coordinates relative to the whole window + // The first parameter to the position function is the mouse position relative to the canvas + const mouseX = canvasMousePos[0] + (divRect?.x || 0); + const mouseY = canvasMousePos[1] + (divRect?.y || 0); + + // The width and height of the tooltip dom element + const tooltipWidth = sizes.contentSize[0]; + const tooltipHeight = sizes.contentSize[1]; + + // Start by placing the tooltip top and right relative to the mouse position + let xPos = mouseX + TOOLTIP_POINTER_MARGIN; + let yPos = mouseY - TOOLTIP_POINTER_MARGIN - tooltipHeight; + + // The tooltip is overflowing past the right edge of the window + if (xPos + tooltipWidth >= document.documentElement.clientWidth) { + // Attempt to place the tooltip to the left of the mouse position + xPos = mouseX - TOOLTIP_POINTER_MARGIN - tooltipWidth; + + // The tooltip is overflowing past the left edge of the window + if (xPos <= 0) + // Place the tooltip a fixed distance from the left edge of the window + xPos = TOOLTIP_OVERFLOW_MARGIN; + } + + // The tooltip is overflowing past the top edge of the window + if (yPos <= 0) { + // Attempt to place the tooltip to the bottom of the mouse position + yPos = mouseY + TOOLTIP_POINTER_MARGIN; + + // The tooltip is overflowing past the bottom edge of the window + if (yPos + tooltipHeight >= document.documentElement.clientHeight) + // Place the tooltip a fixed distance from the top edge of the window + yPos = TOOLTIP_OVERFLOW_MARGIN; + } + + // Return the position (converted back to a relative position on the canvas) + return [xPos - (divRect?.x || 0), yPos - (divRect?.y || 0)]; + }, + }; +} diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts index f138765987d5c..ce00bb71906f1 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts @@ -25,6 +25,7 @@ import transformProps from '../../src/BigNumber/BigNumberWithTrendline/transform import { BigNumberDatum, BigNumberWithTrendlineChartProps, + BigNumberWithTrendlineFormData, } from '../../src/BigNumber/types'; const formData = { @@ -44,7 +45,8 @@ const formData = { datasource: 'test_datasource', }; -const rawFormData = { +const rawFormData: BigNumberWithTrendlineFormData = { + colorPicker: { b: 0, g: 0, r: 0 }, datasource: '1__table', metric: 'value', color_picker: { @@ -129,7 +131,8 @@ describe('BigNumberWithTrendline', () => { expect(transformed.bigNumber).toStrictEqual(1.2345); expect(transformed.bigNumberFallback).not.toBeNull(); - // should successfully formatTime by ganularity + // should successfully formatTime by granularity + // @ts-ignore expect(transformed.formatTime(new Date('2020-01-01'))).toStrictEqual( '2020-01-01 00:00:00', ); @@ -150,6 +153,7 @@ describe('BigNumberWithTrendline', () => { }, }; const transformed = transformProps(propsWithDatasource); + // @ts-ignore expect(transformed.headerFormatter(transformed.bigNumber)).toStrictEqual( '1.23', ); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Graph/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Graph/transformProps.test.ts index 480f4828f3378..61adda8a98c74 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Graph/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Graph/transformProps.test.ts @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, supersetTheme } from '@superset-ui/core'; +import { ChartProps, SqlaFormData, supersetTheme } from '@superset-ui/core'; import transformProps from '../../src/Graph/transformProps'; import { DEFAULT_GRAPH_SERIES_OPTION } from '../../src/Graph/constants'; +import { EchartsGraphChartProps } from '../../src/Graph/types'; describe('EchartsGraph transformProps', () => { it('should transform chart props for viz without category', () => { - const formData = { + const formData: SqlaFormData = { colorScheme: 'bnbColors', datasource: '3__table', granularity_sqla: 'ds', @@ -30,6 +31,7 @@ describe('EchartsGraph transformProps', () => { source: 'source_column', target: 'target_column', category: null, + viz_type: 'graph', }; const queriesData = [ { @@ -57,7 +59,7 @@ describe('EchartsGraph transformProps', () => { }; const chartProps = new ChartProps(chartPropsConfig); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsGraphChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -78,7 +80,11 @@ describe('EchartsGraph transformProps', () => { label: { fontWeight: 'bolder' }, }, symbolSize: 50, - tooltip: { formatter: '{b}: {c}' }, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, value: 6, }, { @@ -91,7 +97,11 @@ describe('EchartsGraph transformProps', () => { label: { fontWeight: 'bolder' }, }, symbolSize: 50, - tooltip: { formatter: '{b}: {c}' }, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, value: 6, }, { @@ -104,7 +114,11 @@ describe('EchartsGraph transformProps', () => { label: { fontWeight: 'bolder' }, }, symbolSize: 10, - tooltip: { formatter: '{b}: {c}' }, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, value: 5, }, { @@ -117,7 +131,11 @@ describe('EchartsGraph transformProps', () => { label: { fontWeight: 'bolder' }, }, symbolSize: 10, - tooltip: { formatter: '{b}: {c}' }, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, value: 5, }, ], @@ -151,7 +169,7 @@ describe('EchartsGraph transformProps', () => { }); it('should transform chart props for viz with category and falsey normalization', () => { - const formData = { + const formData: SqlaFormData = { colorScheme: 'bnbColors', datasource: '3__table', granularity_sqla: 'ds', @@ -160,6 +178,7 @@ describe('EchartsGraph transformProps', () => { target: 'target_column', sourceCategory: 'source_category_column', targetCategory: 'target_category_column', + viz_type: 'graph', }; const queriesData = [ { @@ -197,7 +216,7 @@ describe('EchartsGraph transformProps', () => { }; const chartProps = new ChartProps(chartPropsConfig); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsGraphChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -215,7 +234,11 @@ describe('EchartsGraph transformProps', () => { symbolSize: 10, category: 'category_value_1', select: DEFAULT_GRAPH_SERIES_OPTION.select, - tooltip: DEFAULT_GRAPH_SERIES_OPTION.tooltip, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, label: { show: true }, }, { @@ -225,7 +248,11 @@ describe('EchartsGraph transformProps', () => { symbolSize: 10, category: 'category_value_2', select: DEFAULT_GRAPH_SERIES_OPTION.select, - tooltip: DEFAULT_GRAPH_SERIES_OPTION.tooltip, + tooltip: { + appendToBody: true, + formatter: '{b}: {c}', + position: expect.anything(), + }, label: { show: true }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Tree/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Tree/transformProps.test.ts index ad06455cb11f7..6a56c997c22b6 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Tree/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Tree/transformProps.test.ts @@ -18,6 +18,7 @@ */ import { ChartProps, supersetTheme } from '@superset-ui/core'; import transformProps from '../../src/Tree/transformProps'; +import { EchartsTreeChartProps } from '../../src/Tree/types'; describe('EchartsTree transformProps', () => { const formData = { @@ -70,7 +71,7 @@ describe('EchartsTree transformProps', () => { ]; const chartProps = new ChartProps({ ...chartPropsConfig, queriesData }); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -137,7 +138,7 @@ describe('EchartsTree transformProps', () => { ]; const chartProps = new ChartProps({ ...chartPropsConfig, queriesData }); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -223,7 +224,7 @@ describe('EchartsTree transformProps', () => { ]; const chartProps = new ChartProps({ ...chartPropsConfig, queriesData }); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -299,7 +300,7 @@ describe('EchartsTree transformProps', () => { ]; const chartProps = new ChartProps({ ...chartPropsConfig, queriesData }); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, @@ -385,7 +386,7 @@ describe('EchartsTree transformProps', () => { ]; const chartProps = new ChartProps({ ...chartPropsConfig, queriesData }); - expect(transformProps(chartProps)).toEqual( + expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual( expect.objectContaining({ width: 800, height: 600, diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx index 499f072a50844..2063ab95dee66 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx @@ -144,6 +144,7 @@ export default function PivotTableChart(props: PivotTableProps) { metricColorFormatters, dateFormatters, onContextMenu, + timeGrainSqla, } = props; const theme = useTheme(); @@ -375,14 +376,15 @@ export default function PivotTableChart(props: PivotTableProps) { if (colKey && colKey.length > 1) { colKey.forEach((val, i) => { const col = cols[i]; - const formattedVal = - dateFormatters[col]?.(val as number) || String(val); + const formatter = dateFormatters[col]; + const formattedVal = formatter?.(val as number) || String(val); if (i > 0) { filters.push({ col, op: '==', val, formattedVal, + grain: formatter ? timeGrainSqla : undefined, }); } }); @@ -390,20 +392,21 @@ export default function PivotTableChart(props: PivotTableProps) { if (rowKey) { rowKey.forEach((val, i) => { const col = rows[i]; - const formattedVal = - dateFormatters[col]?.(val as number) || String(val); + const formatter = dateFormatters[col]; + const formattedVal = formatter?.(val as number) || String(val); filters.push({ col, op: '==', val, formattedVal, + grain: formatter ? timeGrainSqla : undefined, }); }); } onContextMenu(e.clientX, e.clientY, filters); } }, - [cols, dateFormatters, onContextMenu, rows], + [cols, dateFormatters, onContextMenu, rows, timeGrainSqla], ); return ( diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/buildQuery.ts b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/buildQuery.ts index b4bff7b45c059..6e33b41cc7909 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/buildQuery.ts @@ -42,7 +42,7 @@ export default function buildQuery(formData: PivotTableQueryFormData) { isPhysicalColumn(col) && formData.time_grain_sqla && hasGenericChartAxes && - formData?.datetime_columns_lookup?.[col] + formData?.temporal_columns_lookup?.[col] ) { return { timeGrain: formData.time_grain_sqla, diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx index 7287d2b9b25ed..a577fb6058d86 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx @@ -97,7 +97,7 @@ const config: ControlPanelConfig = { }, } : null, - hasGenericChartAxes ? 'datetime_columns_lookup' : null, + hasGenericChartAxes ? 'temporal_columns_lookup' : null, ], [ { diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts index dce9f037e25f5..546882274f0f5 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts @@ -101,6 +101,7 @@ export default function transformProps(chartProps: ChartProps) { emitFilter, metricsLayout, conditionalFormatting, + timeGrainSqla, } = formData; const { selectedFilters } = filterState; const granularity = extractTimegrain(rawFormData); @@ -165,5 +166,6 @@ export default function transformProps(chartProps: ChartProps) { metricColorFormatters, dateFormatters, onContextMenu, + timeGrainSqla, }; } diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts b/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts index accd68a2e4e8a..9c0523b582b64 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts @@ -27,6 +27,7 @@ import { QueryFormMetric, QueryFormColumn, BinaryQueryObjectFilterClause, + TimeGranularity, } from '@superset-ui/core'; import { ColorFormatters } from '@superset-ui/chart-controls'; @@ -78,6 +79,7 @@ interface PivotTableCustomizeProps { clientY: number, filters?: BinaryQueryObjectFilterClause[], ) => void; + timeGrainSqla?: TimeGranularity; } export type PivotTableQueryFormData = QueryFormData & diff --git a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts index 6e12123dce123..2c0f3385bd4ef 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts @@ -104,7 +104,7 @@ const buildQuery: BuildQuery = ( isPhysicalColumn(col) && formData.time_grain_sqla && hasGenericChartAxes && - formData?.datetime_columns_lookup?.[col] + formData?.temporal_columns_lookup?.[col] ) { return { timeGrain: formData.time_grain_sqla, diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index f2d3740a782ad..e7e6b3d9852fe 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -218,7 +218,7 @@ const config: ControlPanelConfig = { }, } : null, - hasGenericChartAxes && isAggMode ? 'datetime_columns_lookup' : null, + hasGenericChartAxes && isAggMode ? 'temporal_columns_lookup' : null, ], [ { diff --git a/superset-frontend/spec/fixtures/mockDashboardInfo.js b/superset-frontend/spec/fixtures/mockDashboardInfo.js index c11ec7f88a35d..b2f9f11832232 100644 --- a/superset-frontend/spec/fixtures/mockDashboardInfo.js +++ b/superset-frontend/spec/fixtures/mockDashboardInfo.js @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +import { FilterBarOrientation } from 'src/dashboard/types'; + export default { id: 1234, slug: 'dashboardSlug', @@ -36,4 +38,5 @@ export default { flash_messages: [], conf: { SUPERSET_WEBSERVER_TIMEOUT: 60 }, }, + filterBarOrientation: FilterBarOrientation.VERTICAL, }; diff --git a/superset-frontend/spec/fixtures/mockStore.js b/superset-frontend/spec/fixtures/mockStore.js index 5fe8f54022eb7..119e19a0847d5 100644 --- a/superset-frontend/spec/fixtures/mockStore.js +++ b/superset-frontend/spec/fixtures/mockStore.js @@ -20,6 +20,7 @@ import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import { rootReducer } from 'src/views/store'; +import { FilterBarOrientation } from 'src/dashboard/types'; import mockState from './mockState'; import { @@ -71,60 +72,67 @@ export const mockStoreWithChartsInTabsAndRoot = export const sliceIdWithAppliedFilter = sliceId + 1; export const sliceIdWithRejectedFilter = sliceId + 2; +export const stateWithFilters = { + ...mockState, + dashboardFilters, + dataMask: dataMaskWith2Filters, + charts: { + ...mockState.charts, + [sliceIdWithAppliedFilter]: { + ...mockState.charts[sliceId], + queryResponse: { + status: 'success', + applied_filters: [{ column: 'region' }], + rejected_filters: [], + }, + }, + [sliceIdWithRejectedFilter]: { + ...mockState.charts[sliceId], + queryResponse: { + status: 'success', + applied_filters: [], + rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }], + }, + }, + }, +}; + // has one chart with a filter that has been applied, // one chart with a filter that has been rejected, // and one chart with no filters set. export const getMockStoreWithFilters = () => - createStore(rootReducer, { - ...mockState, - dashboardFilters, - dataMask: dataMaskWith2Filters, - charts: { - ...mockState.charts, - [sliceIdWithAppliedFilter]: { - ...mockState.charts[sliceId], - queryResponse: { - status: 'success', - applied_filters: [{ column: 'region' }], - rejected_filters: [], - }, + createStore(rootReducer, stateWithFilters); + +export const stateWithNativeFilters = { + ...mockState, + nativeFilters, + dataMask: dataMaskWith2Filters, + charts: { + ...mockState.charts, + [sliceIdWithAppliedFilter]: { + ...mockState.charts[sliceId], + queryResponse: { + status: 'success', + applied_filters: [{ column: 'region' }], + rejected_filters: [], }, - [sliceIdWithRejectedFilter]: { - ...mockState.charts[sliceId], - queryResponse: { - status: 'success', - applied_filters: [], - rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }], - }, + }, + [sliceIdWithRejectedFilter]: { + ...mockState.charts[sliceId], + queryResponse: { + status: 'success', + applied_filters: [], + rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }], }, }, - }); + }, + dashboardInfo: { + filterBarOrientation: FilterBarOrientation.VERTICAL, + }, +}; export const getMockStoreWithNativeFilters = () => - createStore(rootReducer, { - ...mockState, - nativeFilters, - dataMask: dataMaskWith2Filters, - charts: { - ...mockState.charts, - [sliceIdWithAppliedFilter]: { - ...mockState.charts[sliceId], - queryResponse: { - status: 'success', - applied_filters: [{ column: 'region' }], - rejected_filters: [], - }, - }, - [sliceIdWithRejectedFilter]: { - ...mockState.charts[sliceId], - queryResponse: { - status: 'success', - applied_filters: [], - rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }], - }, - }, - }, - }); + createStore(rootReducer, stateWithNativeFilters); export const stateWithoutNativeFilters = { ...mockState, @@ -149,6 +157,7 @@ export const stateWithoutNativeFilters = { }, dashboardInfo: { dash_edit_perm: true, + filterBarOrientation: FilterBarOrientation.VERTICAL, metadata: { native_filter_configuration: [], }, diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx index 59a6a5a118c1a..5b2cae174166b 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx @@ -21,7 +21,11 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { render } from 'spec/helpers/testing-library'; import { Store } from 'redux'; -import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; +import { + initialState, + defaultQueryEditor, + extraQueryEditor1, +} from 'src/SqlLab/fixtures'; import EstimateQueryCostButton, { EstimateQueryCostButtonProps, @@ -43,7 +47,7 @@ jest.mock('src/components/Select/AsyncSelect', () => () => ( const setup = (props: Partial, store?: Store) => render( , @@ -61,12 +65,8 @@ describe('EstimateQueryCostButton', () => { }); it('renders label for selected query', async () => { - const queryEditorWithSelectedText = { - ...defaultQueryEditor, - selectedText: 'SELECT', - }; const { queryByText } = setup( - { queryEditor: queryEditorWithSelectedText }, + { queryEditorId: extraQueryEditor1.id }, mockStore(initialState), ); diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx index dc2c23c406263..c0c92e3a5549a 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx @@ -17,43 +17,37 @@ * under the License. */ import React, { useMemo } from 'react'; -import Alert from 'src/components/Alert'; +import { useSelector } from 'react-redux'; import { t } from '@superset-ui/core'; + +import Alert from 'src/components/Alert'; import TableView from 'src/components/TableView'; import Button from 'src/components/Button'; import Loading from 'src/components/Loading'; import ModalTrigger from 'src/components/ModalTrigger'; import { EmptyWrapperType } from 'src/components/TableView/TableView'; -import { - SqlLabRootState, - QueryCostEstimate, - QueryEditor, -} from 'src/SqlLab/types'; -import { getUpToDateQuery } from 'src/SqlLab/actions/sqlLab'; -import { useSelector } from 'react-redux'; +import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; +import { SqlLabRootState, QueryCostEstimate } from 'src/SqlLab/types'; export interface EstimateQueryCostButtonProps { getEstimate: Function; - queryEditor: QueryEditor; + queryEditorId: string; tooltip?: string; disabled?: boolean; } const EstimateQueryCostButton = ({ getEstimate, - queryEditor, + queryEditorId, tooltip = '', disabled = false, }: EstimateQueryCostButtonProps) => { const queryCostEstimate = useSelector< SqlLabRootState, QueryCostEstimate | undefined - >(state => state.sqlLab.queryCostEstimates?.[queryEditor.id]); - const selectedText = useSelector( - rootState => - (getUpToDateQuery(rootState, queryEditor) as unknown as QueryEditor) - .selectedText, - ); + >(state => state.sqlLab.queryCostEstimates?.[queryEditorId]); + + const { selectedText } = useQueryEditor(queryEditorId, ['selectedText']); const { cost } = queryCostEstimate || {}; const tableData = useMemo(() => (Array.isArray(cost) ? cost : []), [cost]); const columns = useMemo( diff --git a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx index 886e139a98e5e..9b45e4b397180 100644 --- a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import { styled, useTheme } from '@superset-ui/core'; + import { AntdDropdown } from 'src/components'; import { Menu } from 'src/components/Menu'; import Icons from 'src/components/Icons'; @@ -83,12 +84,13 @@ const QueryLimitSelect = ({ maxRow, defaultQueryLimit, }: QueryLimitSelectProps) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const queryEditor = useQueryEditor(queryEditorId, ['id', 'queryLimit']); const queryLimit = queryEditor.queryLimit || defaultQueryLimit; - const dispatch = useDispatch(); const setQueryLimit = (updatedQueryLimit: number) => dispatch(queryEditorSetQueryLimit(queryEditor, updatedQueryLimit)); - const theme = useTheme(); return ( diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx index 7062ad694a2e2..276f9b7d19675 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx @@ -24,7 +24,7 @@ import { Store } from 'redux'; import { render, fireEvent, waitFor } from 'spec/helpers/testing-library'; import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; import RunQueryActionButton, { - Props, + RunQueryActionButtonProps, } from 'src/SqlLab/components/RunQueryActionButton'; const middlewares = [thunk]; @@ -51,7 +51,7 @@ const defaultProps = { overlayCreateAsMenu: null, }; -const setup = (props?: Partial, store?: Store) => +const setup = (props?: Partial, store?: Store) => render(, { useRedux: true, ...(store && { store }), diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx index 94a1bcde50f29..9a94fb81c3c9d 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx @@ -27,7 +27,7 @@ import { detectOS } from 'src/utils/common'; import { QueryButtonProps } from 'src/SqlLab/types'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; -export interface Props { +export interface RunQueryActionButtonProps { queryEditorId: string; allowAsync: boolean; queryState?: string; @@ -81,14 +81,14 @@ const StyledButton = styled.span` } `; -const RunQueryActionButton: React.FC = ({ +const RunQueryActionButton = ({ allowAsync = false, queryEditorId, queryState, overlayCreateAsMenu, runQuery, stopQuery, -}) => { +}: RunQueryActionButtonProps) => { const theme = useTheme(); const userOS = detectOS(); diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx index 769b1b4606d4a..f05c008189571 100644 --- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx @@ -61,14 +61,14 @@ const Styles = styled.span` } `; -export default function SaveQuery({ +const SaveQuery = ({ queryEditorId, onSave = () => {}, onUpdate, saveQueryWarning = null, database, columns, -}: SaveQueryProps) { +}: SaveQueryProps) => { const queryEditor = useQueryEditor(queryEditorId, [ 'autorun', 'name', @@ -230,4 +230,6 @@ export default function SaveQuery({ ); -} +}; + +export default SaveQuery; diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx index a7d88483cacb4..73a774964678e 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx @@ -27,7 +27,7 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; -interface ShareSqlLabQueryPropTypes { +interface ShareSqlLabQueryProps { queryEditorId: string; addDangerToast: (msg: string) => void; } @@ -42,10 +42,10 @@ const StyledIcon = styled(Icons.Link)` } `; -function ShareSqlLabQuery({ +const ShareSqlLabQuery = ({ queryEditorId, addDangerToast, -}: ShareSqlLabQueryPropTypes) { +}: ShareSqlLabQueryProps) => { const theme = useTheme(); const { dbId, name, schema, autorun, sql, remoteId, templateParams } = @@ -126,6 +126,6 @@ function ShareSqlLabQuery({ )} ); -} +}; export default withToasts(ShareSqlLabQuery); diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx index c632624838c85..2534a2759e12c 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx @@ -33,13 +33,11 @@ import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper'; import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state'; import SqlEditor from 'src/SqlLab/components/SqlEditor'; import QueryProvider from 'src/views/QueryProvider'; -import { AntdDropdown } from 'src/components'; import { queryEditorSetFunctionNames, queryEditorSetSelectedText, queryEditorSetSchemaOptions, } from 'src/SqlLab/actions/sqlLab'; -import { EmptyStateBig } from 'src/components/EmptyState'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; import { initialState, @@ -130,11 +128,12 @@ describe('SqlEditor', () => { }, ); - it('does not render SqlEditor if no db selected', () => { + it('does not render SqlEditor if no db selected', async () => { const queryEditor = initialState.sqlLab.queryEditors[1]; - const updatedProps = { ...mockedProps, queryEditor }; - const wrapper = buildWrapper(updatedProps); - expect(wrapper.find(EmptyStateBig)).toExist(); + const { findByText } = setup({ ...mockedProps, queryEditor }, store); + expect( + await findByText('Select a database to write a query'), + ).toBeInTheDocument(); }); it('render a SqlEditorLeftBar', async () => { @@ -145,14 +144,13 @@ describe('SqlEditor', () => { }); it('render an AceEditorWrapper', async () => { - const wrapper = buildWrapper(); - await waitForComponentToPaint(wrapper); - expect(wrapper.find(AceEditorWrapper)).toExist(); + const { findByTestId } = setup(mockedProps, store); + expect(await findByTestId('react-ace')).toBeInTheDocument(); }); - it('renders sql from unsaved change', () => { + it('renders sql from unsaved change', async () => { const expectedSql = 'SELECT updated_column\nFROM updated_table\nWHERE'; - const { getByTestId } = setup( + const { findByTestId } = setup( mockedProps, mockStore({ ...initialState, @@ -181,15 +179,16 @@ describe('SqlEditor', () => { }), ); - expect(getByTestId('react-ace')).toHaveTextContent( + expect(await findByTestId('react-ace')).toHaveTextContent( JSON.stringify({ value: expectedSql }).slice(1, -1), ); }); it('render a SouthPane', async () => { - const wrapper = buildWrapper(); - await waitForComponentToPaint(wrapper); - expect(wrapper.find(ConnectedSouthPane)).toExist(); + const { findByText } = setup(mockedProps, store); + expect( + await findByText(/run a query to display results/i), + ).toBeInTheDocument(); }); it('runs query action with ctas false', async () => { @@ -263,8 +262,8 @@ describe('SqlEditor', () => { it('render a Limit Dropdown', async () => { const defaultQueryLimit = 101; const updatedProps = { ...mockedProps, defaultQueryLimit }; - const wrapper = buildWrapper(updatedProps); - await waitForComponentToPaint(wrapper); - expect(wrapper.find(AntdDropdown)).toExist(); + const { findByText } = setup(updatedProps, store); + fireEvent.click(await findByText('LIMIT:')); + expect(await findByText('10 000')).toBeInTheDocument(); }); }); diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index ad1dcc815834b..494d25b3d01c7 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx @@ -467,7 +467,7 @@ const SqlEditor = ({ onChange={params => { dispatch(queryEditorSetTemplateParams(qe, params)); }} - queryEditor={qe} + queryEditorId={qe.id} /> )} @@ -543,7 +543,7 @@ const SqlEditor = ({ @@ -657,9 +657,8 @@ const SqlEditor = ({ > setShowEmptyState(bool)} /> diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index 5f9b140c12faa..c2cf1b5a785fb 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -19,23 +19,17 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; -import { render, screen, act } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import '@testing-library/jest-dom/extend-expect'; import thunk from 'redux-thunk'; import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar'; -import { - table, - initialState, - defaultQueryEditor, - mockedActions, -} from 'src/SqlLab/fixtures'; +import { table, initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; const mockedProps = { - actions: mockedActions, tables: [table], - queryEditor: defaultQueryEditor, + queryEditorId: defaultQueryEditor.id, database: { id: 1, database_name: 'main', @@ -58,103 +52,91 @@ fetchMock.get('glob:*/superset/tables/**', { tableLength: 1, }); -describe('Left Panel Expansion', () => { - test('is valid', () => { - expect( - React.isValidElement( - - - , - ), - ).toBe(true); - }); - - test('renders a TableElement', async () => { - render(, { +const renderAndWait = (props, store) => + waitFor(() => + render(, { useRedux: true, - initialState, - }); + ...(store && { store }), + }), + ); + +test('is valid', () => { + expect( + React.isValidElement( + + + , + ), + ).toBe(true); +}); - expect(await screen.findByText(/Database/i)).toBeInTheDocument(); +test('renders a TableElement', async () => { + await renderAndWait(mockedProps, store); + expect(await screen.findByText(/Database/i)).toBeInTheDocument(); + const tableElement = screen.getAllByTestId('table-element'); + expect(tableElement.length).toBeGreaterThanOrEqual(1); +}); + +test('table should be visible when expanded is true', async () => { + const { container } = await renderAndWait(mockedProps, store); + + const dbSelect = screen.getByRole('combobox', { + name: 'Select database or type database name', + }); + const schemaSelect = screen.getByRole('combobox', { + name: 'Select schema or type schema name', + }); + const dropdown = screen.getByText(/Table/i); + const abUser = screen.queryAllByText(/ab_user/i); + + await waitFor(() => { + expect(screen.getByText(/Database/i)).toBeInTheDocument(); + expect(dbSelect).toBeInTheDocument(); + expect(schemaSelect).toBeInTheDocument(); + expect(dropdown).toBeInTheDocument(); + expect(abUser).toHaveLength(2); expect( - screen.queryAllByTestId('table-element').length, - ).toBeGreaterThanOrEqual(1); + container.querySelector('.ant-collapse-content-active'), + ).toBeInTheDocument(); }); +}); - test('table should be visible when expanded is true', async () => { - const { container } = render(, { - useRedux: true, - initialState, - }); +test('should toggle the table when the header is clicked', async () => { + const store = mockStore(initialState); + await renderAndWait(mockedProps, store); - const dbSelect = screen.getByRole('combobox', { - name: 'Select database or type database name', - }); - const schemaSelect = screen.getByRole('combobox', { - name: 'Select schema or type schema name', - }); - const dropdown = screen.getByText(/Table/i); - const abUser = screen.queryAllByText(/ab_user/i); + const header = (await screen.findAllByText(/ab_user/))[1]; + expect(header).toBeInTheDocument(); + userEvent.click(header); - act(async () => { - expect(await screen.findByText(/Database/i)).toBeInTheDocument(); - expect(dbSelect).toBeInTheDocument(); - expect(schemaSelect).toBeInTheDocument(); - expect(dropdown).toBeInTheDocument(); - expect(abUser).toHaveLength(2); - expect( - container.querySelector('.ant-collapse-content-active'), - ).toBeInTheDocument(); - }); + await waitFor(() => { + expect(store.getActions()).toHaveLength(2); + expect(store.getActions()[1].type).toEqual('COLLAPSE_TABLE'); }); +}); - test('should toggle the table when the header is clicked', async () => { - const collapseMock = jest.fn(); - render( - , - { - useRedux: true, - initialState, - }, - ); +test('When changing database the table list must be updated', async () => { + const { rerender } = await renderAndWait(mockedProps, store); - expect(await screen.findByText(/ab_user/)).toBeInTheDocument(); - const header = screen.getByText(/ab_user/); - userEvent.click(header); - expect(collapseMock).toHaveBeenCalled(); - }); + expect(screen.getAllByText(/main/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/ab_user/i)[0]).toBeInTheDocument(); - test('When changing database the table list must be updated', async () => { - const { rerender } = render(, { + rerender( + , + { useRedux: true, initialState, - }); - - await act(async () => { - expect(await screen.findByText(/main/i)).toBeInTheDocument(); - expect(await screen.findByText(/ab_user/i)).toBeInTheDocument(); - }); - rerender( - , - { - useRedux: true, - initialState, - }, - ); - expect(await screen.findByText(/new_db/i)).toBeInTheDocument(); - expect(await screen.findByText(/new_table/i)).toBeInTheDocument(); - }); + }, + ); + expect(await screen.findByText(/new_db/i)).toBeInTheDocument(); + expect(await screen.findByText(/new_table/i)).toBeInTheDocument(); }); diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx index 06a31711db4a9..4b73cffff986c 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx @@ -18,21 +18,35 @@ */ import React, { useEffect, - useRef, useCallback, useMemo, useState, Dispatch, SetStateAction, } from 'react'; +import { useDispatch } from 'react-redux'; import querystring from 'query-string'; + +import { + queryEditorSetDb, + queryEditorSetFunctionNames, + addTable, + removeTables, + collapseTable, + expandTable, + queryEditorSetSchema, + queryEditorSetTableOptions, + queryEditorSetSchemaOptions, + setDatabases, + addDangerToast, + resetState, +} from 'src/SqlLab/actions/sqlLab'; import Button from 'src/components/Button'; import { t, styled, css, SupersetTheme } from '@superset-ui/core'; import Collapse from 'src/components/Collapse'; import Icons from 'src/components/Icons'; import { TableSelectorMultiple } from 'src/components/TableSelector'; import { IconTooltip } from 'src/components/IconTooltip'; -import { QueryEditor, SchemaOption } from 'src/SqlLab/types'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; import { DatabaseObject } from 'src/components/DatabaseSelector'; import { EmptyStateSmall } from 'src/components/EmptyState'; @@ -41,37 +55,16 @@ import { LocalStorageKeys, setItem, } from 'src/utils/localStorageHelpers'; -import TableElement, { Table, TableElementProps } from '../TableElement'; +import TableElement, { Table } from '../TableElement'; interface ExtendedTable extends Table { expanded: boolean; } -interface actionsTypes { - queryEditorSetDb: (queryEditor: QueryEditor, dbId: number) => void; - queryEditorSetFunctionNames: (queryEditor: QueryEditor, dbId: number) => void; - collapseTable: (table: Table) => void; - expandTable: (table: Table) => void; - addTable: (queryEditor: any, database: any, value: any, schema: any) => void; - setDatabases: (arg0: any) => {}; - addDangerToast: (msg: string) => void; - queryEditorSetSchema: (queryEditor: QueryEditor, schema?: string) => void; - queryEditorSetSchemaOptions: ( - queryEditor: QueryEditor, - options: SchemaOption[], - ) => void; - queryEditorSetTableOptions: ( - queryEditor: QueryEditor, - options: Array, - ) => void; - resetState: () => void; -} - interface SqlEditorLeftBarProps { - queryEditor: QueryEditor; + queryEditorId: string; height?: number; tables?: ExtendedTable[]; - actions: actionsTypes & TableElementProps['actions']; database: DatabaseObject; setEmptyState: Dispatch>; } @@ -102,22 +95,21 @@ const collapseStyles = (theme: SupersetTheme) => css` } `; -export default function SqlEditorLeftBar({ - actions, +const SqlEditorLeftBar = ({ database, - queryEditor, + queryEditorId, tables = [], height = 500, setEmptyState, -}: SqlEditorLeftBarProps) { - // Ref needed to avoid infinite rerenders on handlers - // that require and modify the queryEditor - const queryEditorRef = useRef(queryEditor); +}: SqlEditorLeftBarProps) => { + const dispatch = useDispatch(); + const queryEditor = useQueryEditor(queryEditorId, ['dbId', 'schema']); + const [emptyResultsWithSearch, setEmptyResultsWithSearch] = useState(false); const [userSelectedDb, setUserSelected] = useState( null, ); - const { schema } = useQueryEditor(queryEditor.id, ['schema']); + const { schema } = queryEditor; useEffect(() => { const bool = querystring.parse(window.location.search).db; @@ -132,18 +124,14 @@ export default function SqlEditorLeftBar({ } else setUserSelected(database); }, [database]); - useEffect(() => { - queryEditorRef.current = queryEditor; - }, [queryEditor, database]); - const onEmptyResults = (searchText?: string) => { setEmptyResultsWithSearch(!!searchText); }; const onDbChange = ({ id: dbId }: { id: number }) => { setEmptyState(false); - actions.queryEditorSetDb(queryEditor, dbId); - actions.queryEditorSetFunctionNames(queryEditor, dbId); + dispatch(queryEditorSetDb(queryEditor, dbId)); + dispatch(queryEditorSetFunctionNames(queryEditor, dbId)); }; const selectedTableNames = useMemo( @@ -168,21 +156,21 @@ export default function SqlEditorLeftBar({ }); tablesToAdd.forEach(tableName => - actions.addTable(queryEditor, database, tableName, schemaName), + dispatch(addTable(queryEditor, database, tableName, schemaName)), ); - actions.removeTables(currentTables); + dispatch(removeTables(currentTables)); }; const onToggleTable = (updatedTables: string[]) => { tables.forEach((table: ExtendedTable) => { if (!updatedTables.includes(table.id.toString()) && table.expanded) { - actions.collapseTable(table); + dispatch(collapseTable(table)); } else if ( updatedTables.includes(table.id.toString()) && !table.expanded ) { - actions.expandTable(table); + dispatch(expandTable(table)); } }); }; @@ -225,41 +213,45 @@ export default function SqlEditorLeftBar({ } /> ); - const handleSchemaChange = useCallback( - (schema: string) => { - if (queryEditorRef.current) { - actions.queryEditorSetSchema(queryEditorRef.current, schema); - } - }, - [actions], - ); - const handleTablesLoad = React.useCallback( - (options: Array) => { - if (queryEditorRef.current) { - actions.queryEditorSetTableOptions(queryEditorRef.current, options); - } - }, - [actions], - ); + const handleSchemaChange = useCallback((schema: string) => { + if (queryEditor) { + dispatch(queryEditorSetSchema(queryEditor, schema)); + } + }, []); - const handleSchemasLoad = React.useCallback( - (options: Array) => { - if (queryEditorRef.current) { - actions.queryEditorSetSchemaOptions(queryEditorRef.current, options); - } - }, - [actions], - ); + const handleTablesLoad = useCallback((options: Array) => { + if (queryEditor) { + dispatch(queryEditorSetTableOptions(queryEditor, options)); + } + }, []); + + const handleSchemasLoad = useCallback((options: Array) => { + if (queryEditor) { + dispatch(queryEditorSetSchemaOptions(queryEditor, options)); + } + }, []); + + const handleDbList = useCallback((result: DatabaseObject) => { + dispatch(setDatabases(result)); + }, []); + + const handleError = useCallback((message: string) => { + dispatch(addDangerToast(message)); + }, []); + + const handleResetState = useCallback(() => { + dispatch(resetState()); + }, []); return ( -
+
{tables.map(table => ( - + ))}
@@ -296,11 +288,13 @@ export default function SqlEditorLeftBar({ )}
); -} +}; + +export default SqlEditorLeftBar; diff --git a/superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.jsx b/superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.tsx similarity index 72% rename from superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.jsx rename to superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.tsx index 08923c5d9e9e8..fb8ee55992301 100644 --- a/superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.jsx +++ b/superset-frontend/src/SqlLab/components/TabStatusIcon/TabStatusIcon.test.tsx @@ -16,24 +16,22 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryState } from '@superset-ui/core'; import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; +import { render } from 'spec/helpers/testing-library'; import TabStatusIcon from 'src/SqlLab/components/TabStatusIcon'; function setup() { - const onClose = sinon.spy(); - const wrapper = shallow( - , - ); - return { wrapper, onClose }; + return render(); } describe('TabStatusIcon', () => { it('renders a circle without an x when hovered', () => { - const { wrapper } = setup(); - expect(wrapper.find('div.circle')).toExist(); - expect(wrapper.text()).toBe(''); + const { container } = setup(); + expect(container.getElementsByClassName('circle')[0]).toBeInTheDocument(); + expect( + container.getElementsByClassName('circle')[0]?.textContent ?? 'undefined', + ).toBe(''); }); }); diff --git a/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx b/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx index 799124fb9c099..ab6348e835026 100644 --- a/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx +++ b/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx @@ -17,12 +17,33 @@ * under the License. */ import React from 'react'; -import { QueryState } from '@superset-ui/core'; +import { QueryState, styled } from '@superset-ui/core'; +import Icons, { IconType } from 'src/components/Icons'; + +const IconContainer = styled.span` + position: absolute; + top: -7px; + left: 0px; +`; interface TabStatusIconProps { tabState: QueryState; } +const STATE_ICONS: Record> = { + success: Icons.Check, + failed: Icons.CancelX, +}; + export default function TabStatusIcon({ tabState }: TabStatusIconProps) { - return
; + const StatusIcon = STATE_ICONS[tabState]; + return ( +
+ {StatusIcon && ( + + + + )} +
+ ); } diff --git a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx index 91bddc57fb2b3..60efa3a59677f 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx +++ b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx @@ -17,22 +17,24 @@ * under the License. */ import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; import Collapse from 'src/components/Collapse'; import { IconTooltip } from 'src/components/IconTooltip'; import TableElement from 'src/SqlLab/components/TableElement'; import ColumnElement from 'src/SqlLab/components/ColumnElement'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; -import { mockedActions, table } from 'src/SqlLab/fixtures'; +import { initialState, table } from 'src/SqlLab/fixtures'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); describe('TableElement', () => { - const mockStore = configureStore([]); - const store = mockStore({}); + const store = mockStore(initialState); const mockedProps = { - actions: mockedActions, table, timeout: 0, }; @@ -57,7 +59,17 @@ describe('TableElement', () => { expect(wrapper.find(IconTooltip)).toHaveLength(4); }); it('has 14 columns', () => { - const wrapper = shallow(); + const wrapper = mount( + + + , + { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { + theme: supersetTheme, + }, + }, + ); expect(wrapper.find(ColumnElement)).toHaveLength(14); }); it('mounts', () => { @@ -143,6 +155,7 @@ describe('TableElement', () => { }, ); wrapper.find('.table-remove').hostNodes().simulate('click'); - expect(mockedActions.removeDataPreview.called).toBe(true); + expect(store.getActions()).toHaveLength(1); + expect(store.getActions()[0].type).toEqual('REMOVE_DATA_PREVIEW'); }); }); diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx index 1ecc7822203fa..44fbe6e1cc0cd 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx +++ b/superset-frontend/src/SqlLab/components/TableElement/index.tsx @@ -17,12 +17,14 @@ * under the License. */ import React, { useState, useRef } from 'react'; +import { useDispatch } from 'react-redux'; import Collapse from 'src/components/Collapse'; import Card from 'src/components/Card'; import ButtonGroup from 'src/components/ButtonGroup'; import { t, styled } from '@superset-ui/core'; import { debounce } from 'lodash'; +import { removeDataPreview, removeTables } from 'src/SqlLab/actions/sqlLab'; import { Tooltip } from 'src/components/Tooltip'; import CopyToClipboard from 'src/components/CopyToClipboard'; import { IconTooltip } from 'src/components/IconTooltip'; @@ -55,10 +57,6 @@ export interface Table { export interface TableElementProps { table: Table; - actions: { - removeDataPreview: (table: Table) => void; - removeTables: (tables: Table[]) => void; - }; } const StyledSpan = styled.span` @@ -74,7 +72,9 @@ const Fade = styled.div` opacity: ${(props: { hovered: boolean }) => (props.hovered ? 1 : 0)}; `; -const TableElement = ({ table, actions, ...props }: TableElementProps) => { +const TableElement = ({ table, ...props }: TableElementProps) => { + const dispatch = useDispatch(); + const [sortColumns, setSortColumns] = useState(false); const [hovered, setHovered] = useState(false); const tableNameRef = useRef(null); @@ -84,8 +84,8 @@ const TableElement = ({ table, actions, ...props }: TableElementProps) => { }; const removeTable = () => { - actions.removeDataPreview(table); - actions.removeTables([table]); + dispatch(removeDataPreview(table)); + dispatch(removeTables([table])); }; const toggleSortColumns = () => { diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx index 886edb7afc120..fdf8fd3b53f57 100644 --- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx +++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx @@ -30,7 +30,7 @@ import { import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; import TemplateParamsEditor, { - Props, + TemplateParamsEditorProps, } from 'src/SqlLab/components/TemplateParamsEditor'; jest.mock('src/components/DeprecatedSelect', () => () => ( @@ -50,12 +50,15 @@ jest.mock('src/components/AsyncAceEditor', () => ({ const middlewares = [thunk]; const mockStore = configureStore(middlewares); -const setup = (otherProps: Partial = {}, store?: Store) => +const setup = ( + otherProps: Partial = {}, + store?: Store, +) => render( {}} - queryEditor={defaultQueryEditor} + queryEditorId={defaultQueryEditor.id} {...otherProps} />, { diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx index 4eea10da05fe7..8943c2b8d5d48 100644 --- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx @@ -17,18 +17,16 @@ * under the License. */ import React, { useState, useEffect } from 'react'; -import Badge from 'src/components/Badge'; import { t, styled } from '@superset-ui/core'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import { debounce } from 'lodash'; +import Badge from 'src/components/Badge'; import ModalTrigger from 'src/components/ModalTrigger'; import { ConfigEditor } from 'src/components/AsyncAceEditor'; import { FAST_DEBOUNCE } from 'src/constants'; import { Tooltip } from 'src/components/Tooltip'; -import { useSelector } from 'react-redux'; -import { QueryEditor, SqlLabRootState } from 'src/SqlLab/types'; -import { getUpToDateQuery } from 'src/SqlLab/actions/sqlLab'; +import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; const StyledConfigEditor = styled(ConfigEditor)` &.ace_editor { @@ -36,24 +34,22 @@ const StyledConfigEditor = styled(ConfigEditor)` } `; -export type Props = { - queryEditor: QueryEditor; +export type TemplateParamsEditorProps = { + queryEditorId: string; language: 'yaml' | 'json'; onChange: () => void; }; -function TemplateParamsEditor({ - queryEditor, +const TemplateParamsEditor = ({ + queryEditorId, language, onChange = () => {}, -}: Props) { +}: TemplateParamsEditorProps) => { const [parsedJSON, setParsedJSON] = useState({}); const [isValid, setIsValid] = useState(true); - const code = useSelector( - rootState => - (getUpToDateQuery(rootState, queryEditor) as unknown as QueryEditor) - .templateParams || '{}', - ); + + const { templateParams } = useQueryEditor(queryEditorId, ['templateParams']); + const code = templateParams ?? '{}'; useEffect(() => { try { @@ -125,6 +121,6 @@ function TemplateParamsEditor({ modalBody={modalBody} /> ); -} +}; export default TemplateParamsEditor; diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts index bb38fe6873a82..878c49faae43f 100644 --- a/superset-frontend/src/SqlLab/fixtures.ts +++ b/superset-frontend/src/SqlLab/fixtures.ts @@ -203,6 +203,7 @@ export const extraQueryEditor1 = { id: 'diekd23', sql: 'SELECT *\nFROM\nWHERE\nLIMIT', name: 'Untitled Query 2', + selectedText: 'SELECT', }; export const extraQueryEditor2 = { diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less index bec202b7bceba..aa75c0ec00b63 100644 --- a/superset-frontend/src/SqlLab/main.less +++ b/superset-frontend/src/SqlLab/main.less @@ -161,11 +161,12 @@ div.Workspace { vertical-align: middle; font-size: @font-size-m; font-weight: @font-weight-bold; + color: @lightest; + position: relative; } .running { - background-color: fade(@success, @opacity-heavy); - color: @darkest; + background-color: @info; } .success { diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx index 30ea2293a1cd5..e90b30e1d7a7f 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx @@ -243,6 +243,7 @@ export default function DrillDetailPane({ margin-bottom: 0; } `} + scrollTopOnPagination /> ); } diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 26b118bcac83d..3b10e70f20901 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -92,7 +92,7 @@ export interface DatabaseSelectorProps { db?: DatabaseObject | null; emptyState?: ReactNode; formMode?: boolean; - getDbList?: (arg0: any) => {}; + getDbList?: (arg0: any) => void; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: DatabaseObject) => void; @@ -221,6 +221,13 @@ export default function DatabaseSelector({ ); }, [db]); + function changeSchema(schema: SchemaValue) { + setCurrentSchema(schema); + if (onSchemaChange) { + onSchemaChange(schema.value); + } + } + useEffect(() => { if (currentDb) { setLoadingSchemas(true); @@ -240,6 +247,7 @@ export default function DatabaseSelector({ } setSchemaOptions(options); setLoadingSchemas(false); + if (options.length === 1) changeSchema(options[0]); if (refresh > 0) addSuccessToast('List refreshed'); }) .catch(err => { @@ -270,13 +278,6 @@ export default function DatabaseSelector({ } } - function changeSchema(schema: SchemaValue) { - setCurrentSchema(schema); - if (onSchemaChange) { - onSchemaChange(schema.value); - } - } - function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) { return (
diff --git a/superset-frontend/src/components/Datasource/ChangeDatasourceModal.tsx b/superset-frontend/src/components/Datasource/ChangeDatasourceModal.tsx index a7c29c51d91f1..fc31c0ec7c513 100644 --- a/superset-frontend/src/components/Datasource/ChangeDatasourceModal.tsx +++ b/superset-frontend/src/components/Datasource/ChangeDatasourceModal.tsx @@ -251,7 +251,7 @@ const ChangeDatasourceModal: FunctionComponent = ({ show={show} onHide={onHide} responsive - title={t('Change dataset')} + title={t('Swap dataset')} width={confirmChange ? '432px' : ''} height={confirmChange ? 'auto' : '540px'} hideFooter={!confirmChange} diff --git a/superset-frontend/src/components/DropdownContainer/index.tsx b/superset-frontend/src/components/DropdownContainer/index.tsx index 6111698f05a84..d364d3542ee73 100644 --- a/superset-frontend/src/components/DropdownContainer/index.tsx +++ b/superset-frontend/src/components/DropdownContainer/index.tsx @@ -104,7 +104,7 @@ const DropdownContainer = forwardRef( { items, onOverflowingStateChange, - dropdownContent: popoverContent, + dropdownContent: getPopoverContent, dropdownRef: popoverRef, dropdownStyle: popoverStyle = {}, dropdownTriggerCount: popoverTriggerCount, @@ -209,26 +209,31 @@ const DropdownContainer = forwardRef( } }, [notOverflowedIds, onOverflowingStateChange, overflowedIds]); - const content = useMemo( - () => ( -
- {popoverContent - ? popoverContent(overflowedItems) - : overflowedItems.map(item => item.element)} -
- ), + const overflowingCount = + overflowingIndex !== -1 ? items.length - overflowingIndex : 0; + + const popoverContent = useMemo( + () => + getPopoverContent || overflowingCount ? ( +
+ {getPopoverContent + ? getPopoverContent(overflowedItems) + : overflowedItems.map(item => item.element)} +
+ ) : null, [ + getPopoverContent, overflowedItems, - popoverContent, + overflowingCount, popoverRef, popoverStyle, theme.gridUnit, @@ -244,22 +249,19 @@ const DropdownContainer = forwardRef( [ref], ); - const overflowingCount = - overflowingIndex !== -1 ? items.length - overflowingIndex : 0; - return (
{notOverflowedItems.map(item => item.element)}
- {overflowingCount > 0 && ( + {popoverContent && ( setPopoverVisible(visible)} + placement="bottom" > diff --git a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx index 228c6889a22e7..b6c5d89a2e4e7 100644 --- a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx +++ b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx @@ -43,6 +43,7 @@ const StyledDropdownButton = styled( height: unset; padding: 0; border: none; + width: auto !important; .anticon { line-height: 0; diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index 7b8488a5b9267..e509521d3fa34 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -132,7 +132,7 @@ export default function ErrorAlert({ {description && (

{description}

- {!isExpandable && ( + {!isExpandable && body && ( ` (ICON_WIDTH + SPACE_BETWEEN_ITEMS) * count - SPACE_BETWEEN_ITEMS }px; + border-radius: ${theme.borderRadius}px; + line-height: 1; `} `; @@ -68,6 +70,7 @@ const StyledItem = styled.div<{ }>` ${({ theme, collapsed, last, onClick }) => ` display: flex; + align-items: center; max-width: ${ ICON_WIDTH + ICON_PADDING + @@ -91,6 +94,9 @@ const StyledItem = styled.div<{ : theme.colors.grayscale.base }; padding-right: ${collapsed ? 0 : ICON_PADDING}px; + & .anticon { + line-height: 0; + } } & .metadata-text { min-width: ${TEXT_MIN_WIDTH}px; diff --git a/superset-frontend/src/components/Table/Table.overview.mdx b/superset-frontend/src/components/Table/Table.overview.mdx index 8341db879fcb5..2fa1455cd0eda 100644 --- a/superset-frontend/src/components/Table/Table.overview.mdx +++ b/superset-frontend/src/components/Table/Table.overview.mdx @@ -183,7 +183,7 @@ The table displays a set number of rows at a time, the user navigates the table The default page size and page size options for the menu are configurable via the `pageSizeOptions` and `defaultPageSize` props. NOTE: Pagination controls will only display when the data for the table has more records than the default page size. - + ``` @@ -191,6 +191,85 @@ NOTE: Pagination controls will only display when the data for the table has more --- +### Server Pagination + +The table can be configured for async data fetching to get partial data sets while showing pagination controls that let the user navigate through data. +To override the default paging, which uses `data.length` to determine the record count, populate the `recordCount` prop with the total number of records +contained in the dataset on the server being paged through. When the user navigates through the paged data it will invoke the `onChange` callback +function enabling data fetching to occur when the user changes the page. + + + +``` +interface BasicData { + name: string; + category: string; + price: number; + description?: string; + key: number; +} + +const generateData = (startIndex: number, pageSize: number): BasicData[] => { + const data: BasicData[] = []; + for (let i = 0; i < pageSize; i += 1) { + const recordIndex = startIndex + i; + data.push({ + key: recordIndex, + name: `Dynamic Record ${recordIndex}`, + category: 'Disk Storage', + price: recordIndex * 2.59, + description: 'A random description', + }); + } + return data; +}; + +const ServerPaginationTable = () => { + const [data, setData] = useState(generateData(0, 5)); + const [loading, setLoading] = useState(false); + // This should really be calculated server side for the data set being paged through + const recordCount = 5000; + + const handleChange = (pagination: TablePaginationConfig) => { + const pageSize = pagination?.pageSize ?? 5; + const current = pagination?.current ?? 0; + setLoading(true); + // simulate a fetch + setTimeout(() => { + setData(generateData(current * pageSize, pageSize)); + setLoading(false); + }, 1000); + }; + + return ( +
+ ); +}; +``` + +--- + +### Virtualization for Performance + +Table virtualization can enable viewing data with many columns and/or rows. +Virtualization can be enabled via the `virtualize` prop. + +NOTE: Row event handlers will be ignored when table is running with `virtualize={true}`. +Support for row event handlers may be added in future versions of the Table. + + + +--- + ## Integration Checklist The following specifications are required every time a table is used. These choices should be intentional based on the specific user needs for the table instance. diff --git a/superset-frontend/src/components/Table/Table.stories.tsx b/superset-frontend/src/components/Table/Table.stories.tsx index 90ee3448c67ec..8ca004dc09a50 100644 --- a/superset-frontend/src/components/Table/Table.stories.tsx +++ b/superset-frontend/src/components/Table/Table.stories.tsx @@ -19,8 +19,14 @@ import React, { useState } from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { action } from '@storybook/addon-actions'; -import { supersetTheme, ThemeProvider } from '@superset-ui/core'; -import { Table, TableSize, SUPERSET_TABLE_COLUMN, ColumnsType } from './index'; +import { + Table, + TableSize, + SUPERSET_TABLE_COLUMN, + ColumnsType, + OnChangeFunction, + ETableAction, +} from './index'; import { numericalSort, alphabeticalSort } from './sorters'; import ButtonCell from './cell-renderers/ButtonCell'; import ActionCell from './cell-renderers/ActionCell'; @@ -62,10 +68,10 @@ export interface ExampleData { key: number; } -function generateValues(amount: number): object { +function generateValues(amount: number, row = 0): object { const cells = {}; for (let i = 0; i < amount; i += 1) { - cells[`col-${i}`] = `Text ${i}`; + cells[`col-${i}`] = i * row * 0.75; } return cells; } @@ -74,15 +80,24 @@ function generateColumns(amount: number): ColumnsType[] { const newCols: any[] = []; for (let i = 0; i < amount; i += 1) { newCols.push({ - title: `Column Header ${i}`, + title: `C${i}`, dataIndex: `col-${i}`, key: `col-${i}`, + width: 90, + render: (value: number) => ( + + ), + sorter: (a: BasicData, b: BasicData) => numericalSort(`col-${i}`, a, b), }); } return newCols as ColumnsType[]; } -const recordCount = 200; -const columnCount = 12; +const recordCount = 500; +const columnCount = 500; const randomCols: ColumnsType[] = generateColumns(columnCount); const basicData: BasicData[] = [ @@ -107,6 +122,41 @@ const basicData: BasicData[] = [ price: 49.99, description: 'Reliable and fast data storage', }, + { + key: 4, + name: '128 GB SSD', + category: 'Hardrive', + price: 49.99, + description: 'Reliable and fast data storage', + }, + { + key: 5, + name: '4GB 144mhz', + category: 'Memory', + price: 19.99, + description: 'Laptop memory', + }, + { + key: 6, + name: '1GB USB Flash Drive', + category: 'Portable Storage', + price: 9.99, + description: 'USB Flash Drive portal data storage', + }, + { + key: 7, + name: '256 GB SSD', + category: 'Hardrive', + price: 175, + description: 'Reliable and fast data storage', + }, + { + key: 8, + name: '1 TB SSD', + category: 'Hardrive', + price: 349.99, + description: 'Reliable and fast data storage', + }, ]; const basicColumns: ColumnsType = [ @@ -114,7 +164,7 @@ const basicColumns: ColumnsType = [ title: 'Name', dataIndex: 'name', key: 'name', - width: 150, + width: 100, sorter: (a: BasicData, b: BasicData) => alphabeticalSort('name', a, b), }, { @@ -128,6 +178,7 @@ const basicColumns: ColumnsType = [ dataIndex: 'price', key: 'price', sorter: (a: BasicData, b: BasicData) => numericalSort('price', a, b), + width: 100, }, { title: 'Description', @@ -141,25 +192,20 @@ const bigColumns: ColumnsType = [ title: 'Name', dataIndex: 'name', key: 'name', - render: (text: string, row: object, index: number) => ( - - ), width: 150, }, { title: 'Age', dataIndex: 'age', key: 'age', + sorter: (a: ExampleData, b: ExampleData) => numericalSort('age', a, b), + width: 75, }, { title: 'Address', dataIndex: 'address', key: 'address', + width: 100, }, ...(randomCols as ColumnsType), ]; @@ -253,17 +299,11 @@ for (let i = 0; i < recordCount; i += 1) { name: `Dynamic record ${i}`, age: 32 + i, address: `DynamoCity, Dynamic Lane no. ${i}`, - ...generateValues(columnCount), + ...generateValues(columnCount, i), }); } -export const Basic: ComponentStory = args => ( - -
-
- - -); +export const Basic: ComponentStory = args =>
; function handlers(record: object, rowIndex: number) { return { @@ -286,31 +326,150 @@ Basic.args = { columns: basicColumns, size: TableSize.SMALL, onRow: handlers, + usePagination: false, +}; + +export const Pagination: ComponentStory = args => ( +
+); + +Pagination.args = { + data: basicData, + columns: basicColumns, + size: TableSize.SMALL, pageSizeOptions: ['5', '10', '15', '20', '25'], - defaultPageSize: 10, + defaultPageSize: 5, }; -export const ManyColumns: ComponentStory = args => ( - -
-
- - +const generateData = (startIndex: number, pageSize: number): BasicData[] => { + const data: BasicData[] = []; + for (let i = 0; i < pageSize; i += 1) { + const recordIndex = startIndex + i; + data.push({ + key: recordIndex, + name: `Dynamic Record ${recordIndex}`, + category: 'Disk Storage', + price: recordIndex * 2.59, + description: 'A random description', + }); + } + return data; +}; + +const paginationColumns: ColumnsType = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 100, + }, + { + title: 'Category', + dataIndex: 'category', + key: 'category', + }, + { + title: 'Price', + dataIndex: 'price', + key: 'price', + width: 100, + render: (value: number) => ( + + ), + sorter: (a: BasicData, b: BasicData) => numericalSort('price', a, b), + }, + { + title: 'Description', + dataIndex: 'description', + key: 'description', + }, + { + dataIndex: 'actions', + key: 'actions', + render: (text: string, row: object) => ( + + ), + width: 32, + fixed: 'right', + }, +]; + +export const ServerPagination: ComponentStory = args => { + const [data, setData] = useState(generateData(0, 5)); + const [loading, setLoading] = useState(false); + + const handleChange: OnChangeFunction = ( + pagination, + filters, + sorter, + extra, + ) => { + const pageSize = pagination?.pageSize ?? 5; + const current = pagination?.current ?? 0; + switch (extra?.action) { + case ETableAction.PAGINATE: { + setLoading(true); + // simulate a fetch + setTimeout(() => { + setData(generateData(current * pageSize, pageSize)); + setLoading(false); + }, 1000); + break; + } + case ETableAction.SORT: { + action(`table-sort-change: ${JSON.stringify(sorter)}`); + break; + } + case ETableAction.FILTER: { + action(`table-sort-change: ${JSON.stringify(filters)}`); + break; + } + default: { + action('table action unknown'); + break; + } + } + }; + + return ( +
+ ); +}; + +ServerPagination.args = { + columns: paginationColumns, + size: TableSize.SMALL, + pageSizeOptions: ['5', '20', '50'], + defaultPageSize: 5, +}; + +export const VirtualizedPerformance: ComponentStory = args => ( +
); -ManyColumns.args = { +VirtualizedPerformance.args = { data: bigdata, columns: bigColumns, size: TableSize.SMALL, resizable: true, reorderable: true, height: 350, + virtualize: true, + usePagination: false, }; export const Loading: ComponentStory = args => ( - -
- +
); Loading.args = { @@ -321,11 +480,7 @@ Loading.args = { }; export const ResizableColumns: ComponentStory = args => ( - -
-
- - +
); ResizableColumns.args = { @@ -362,26 +517,24 @@ export const ReorderableColumns: ComponentStory = args => { setDroppedItem(data); }; return ( - -
-
) => dragOver(ev)} - onDragLeave={(ev: React.DragEvent) => dragOut(ev)} - onDrop={(ev: React.DragEvent) => dragDrop(ev)} - style={{ - width: '100%', - height: '40px', - border: '1px solid grey', - marginBottom: '8px', - padding: '8px', - borderRadius: '4px', - }} - > - {droppedItem ?? 'Drop column here...'} -
-
+
+
) => dragOver(ev)} + onDragLeave={(ev: React.DragEvent) => dragOut(ev)} + onDrop={(ev: React.DragEvent) => dragDrop(ev)} + style={{ + width: '100%', + height: '40px', + border: '1px solid grey', + marginBottom: '8px', + padding: '8px', + borderRadius: '4px', + }} + > + {droppedItem ?? 'Drop column here...'}
- +
+ ); }; @@ -417,11 +570,7 @@ const rendererData: RendererData[] = [ ]; export const CellRenderers: ComponentStory = args => ( - -
-
- - +
); CellRenderers.args = { diff --git a/superset-frontend/src/components/Table/VirtualTable.tsx b/superset-frontend/src/components/Table/VirtualTable.tsx new file mode 100644 index 0000000000000..713eca6b79a60 --- /dev/null +++ b/superset-frontend/src/components/Table/VirtualTable.tsx @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Table as AntTable } from 'antd'; +import classNames from 'classnames'; +import { useResizeDetector } from 'react-resize-detector'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; +import { VariableSizeGrid as Grid } from 'react-window'; +import { StyledComponent } from '@emotion/styled'; +import { useTheme, styled } from '@superset-ui/core'; +import { TablePaginationConfig } from 'antd/lib/table'; +import { TableProps, TableSize, HEIGHT_OFFSET, ETableAction } from './index'; + +const StyledCell: StyledComponent = styled('div')( + ({ theme, height }) => ` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-left: ${theme.gridUnit * 2}px; + padding-right: ${theme.gridUnit}px; + border-bottom: 1px solid ${theme.colors.grayscale.light3}; + transition: background 0.3s; + line-height: ${height}px; + box-sizing: border-box; +`, +); + +const StyledTable: StyledComponent = styled(AntTable)( + ({ theme }) => ` + th.ant-table-cell { + font-weight: ${theme.typography.weights.bold}; + color: ${theme.colors.grayscale.dark1}; + user-select: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .ant-pagination-item-active { + border-color: ${theme.colors.primary.base}; + } + } +`, +); + +const SMALL = 39; +const MIDDLE = 47; + +const VirtualTable = (props: TableProps) => { + const { columns, pagination, onChange, height, scroll, size } = props; + const [tableWidth, setTableWidth] = useState(0); + const onResize = useCallback((width: number) => { + setTableWidth(width); + }, []); + const { ref } = useResizeDetector({ onResize }); + const theme = useTheme(); + + // If a column definition has no width, react-window will use this as the default column width + const DEFAULT_COL_WIDTH = theme?.gridUnit * 37 || 150; + const widthColumnCount = columns!.filter(({ width }) => !width).length; + let staticColWidthTotal = 0; + columns?.forEach(column => { + if (column.width) { + staticColWidthTotal += column.width as number; + } + }); + + let totalWidth = 0; + const defaultWidth = Math.max( + Math.floor((tableWidth - staticColWidthTotal) / widthColumnCount), + 50, + ); + + const mergedColumns = + columns?.map?.(column => { + const modifiedColumn = { ...column }; + if (!column.width) { + modifiedColumn.width = defaultWidth; + } + totalWidth += modifiedColumn.width as number; + return modifiedColumn; + }) ?? []; + + /* + * There are cases where a user could set the width of each column and the total width is less than width of + * the table. In this case we will stretch the last column to use the extra space + */ + if (totalWidth < tableWidth) { + const lastColumn = mergedColumns[mergedColumns.length - 1]; + lastColumn.width = + (lastColumn.width as number) + Math.floor(tableWidth - totalWidth); + } + + const gridRef = useRef(); + const [connectObject] = useState(() => { + const obj = {}; + Object.defineProperty(obj, 'scrollLeft', { + get: () => { + if (gridRef.current) { + return gridRef.current?.state?.scrollLeft; + } + return null; + }, + set: (scrollLeft: number) => { + if (gridRef.current) { + gridRef.current.scrollTo({ scrollLeft }); + } + }, + }); + + return obj; + }); + + const resetVirtualGrid = () => { + gridRef.current?.resetAfterIndices({ + columnIndex: 0, + shouldForceUpdate: true, + }); + }; + + useEffect(() => resetVirtualGrid, [tableWidth, columns, size]); + + /* + * antd Table has a runtime error when it tries to fire the onChange event triggered from a pageChange + * when the table body is overridden with the virtualized table. This function capture the page change event + * from within the pagination controls and proxies the onChange event payload + */ + const onPageChange = (page: number, size: number) => { + /** + * This resets vertical scroll position to 0 (top) when page changes + * We intentionally leave horizontal scroll where it was so user can focus on + * specific range of columns as they page through data + */ + gridRef.current?.scrollTo?.({ scrollTop: 0 }); + + onChange?.( + { + ...pagination, + current: page, + pageSize: size, + } as TablePaginationConfig, + {}, + {}, + { + action: ETableAction.PAGINATE, + currentDataSource: [], + }, + ); + }; + + const renderVirtualList = (rawData: object[], { ref, onScroll }: any) => { + // eslint-disable-next-line no-param-reassign + ref.current = connectObject; + const cellSize = size === TableSize.MIDDLE ? MIDDLE : SMALL; + return ( + { + const { width = DEFAULT_COL_WIDTH } = mergedColumns[index]; + return width as number; + }} + height={height ? height - HEIGHT_OFFSET : (scroll!.y as number)} + rowCount={rawData.length} + rowHeight={() => cellSize} + width={tableWidth} + onScroll={({ scrollLeft }: { scrollLeft: number }) => { + onScroll({ scrollLeft }); + }} + > + {({ + columnIndex, + rowIndex, + style, + }: { + columnIndex: number; + rowIndex: number; + style: React.CSSProperties; + }) => { + const data: any = rawData?.[rowIndex]; + // Set default content + let content = + data?.[(mergedColumns as any)?.[columnIndex]?.dataIndex]; + // Check if the column has a render function + const render = mergedColumns[columnIndex]?.render; + if (typeof render === 'function') { + // Use render function to generate formatted content using column's render function + content = render(content, data, rowIndex); + } + + return ( + + {content} + + ); + }} + + ); + }; + + const modifiedPagination = { + ...pagination, + onChange: onPageChange, + }; + + return ( +
+ +
+ ); +}; + +export default VirtualTable; diff --git a/superset-frontend/src/components/Table/index.tsx b/superset-frontend/src/components/Table/index.tsx index d5f449c752875..99d70312da056 100644 --- a/superset-frontend/src/components/Table/index.tsx +++ b/superset-frontend/src/components/Table/index.tsx @@ -18,22 +18,29 @@ */ import React, { useState, useEffect, useRef, ReactElement } from 'react'; import { Table as AntTable, ConfigProvider } from 'antd'; -import type { +import { ColumnType, ColumnGroupType, TableProps as AntTableProps, } from 'antd/es/table'; +import { PaginationProps } from 'antd/es/pagination'; +import { Key } from 'antd/lib/table/interface'; import { t, useTheme, logging } from '@superset-ui/core'; import Loading from 'src/components/Loading'; import styled, { StyledComponent } from '@emotion/styled'; import InteractiveTableUtils from './utils/InteractiveTableUtils'; +import VirtualTable from './VirtualTable'; export const SUPERSET_TABLE_COLUMN = 'superset/table-column'; export interface TableDataType { key: React.Key; } -export declare type ColumnsType = ( +export interface TablePaginationConfig extends PaginationProps { + extra?: object; +} + +export type ColumnsType = ( | ColumnGroupType | ColumnType )[]; @@ -67,6 +74,32 @@ export interface Locale { cancelSort: string; } +export type SortOrder = 'descend' | 'ascend' | null; +export interface SorterResult { + column?: ColumnType; + order?: SortOrder; + field?: Key | Key[]; + columnKey?: Key; +} + +export enum ETableAction { + PAGINATE = 'paginate', + SORT = 'sort', + FILTER = 'filter', +} + +export interface TableCurrentDataSource { + currentDataSource: RecordType[]; + action: ETableAction; +} + +export type OnChangeFunction = ( + pagination: TablePaginationConfig, + filters: Record, + sorter: SorterResult | SorterResult[], + extra: TableCurrentDataSource, +) => void; + export interface TableProps extends AntTableProps { /** * Data that will populate the each row and map to the column key. @@ -108,6 +141,10 @@ export interface TableProps extends AntTableProps { * EXPERIMENTAL: Controls if columns are re-orderable by user drag drop. */ reorderable?: boolean; + /** + * Controls if pagination is active or disabled. + */ + usePagination?: boolean; /** * Default number of rows table will display per page of data. */ @@ -134,6 +171,27 @@ export interface TableProps extends AntTableProps { * when the number of rows exceeds the visible space. */ height?: number; + /** + * Sets the table to use react-window for scroll virtualization in cases where + * there are unknown amount of columns, or many, many rows + */ + virtualize?: boolean; + /** + * Used to override page controls total record count when using server-side paging. + */ + recordCount?: number; + /** + * Invoked when the tables sorting, paging, or filtering is changed. + */ + onChange?: OnChangeFunction; +} + +interface IPaginationOptions { + hideOnSinglePage: boolean; + pageSize: number; + pageSizeOptions: string[]; + onShowSizeChange: Function; + total?: number; } export enum TableSize { @@ -143,12 +201,12 @@ export enum TableSize { const defaultRowSelection: React.Key[] = []; // This accounts for the tables header and pagination if user gives table instance a height. this is a temp solution -const HEIGHT_OFFSET = 108; +export const HEIGHT_OFFSET = 108; -const StyledTable: StyledComponent = styled(AntTable)` - ${({ theme, height }) => ` +const StyledTable: StyledComponent = styled(AntTable)( + ({ theme, height }) => ` .ant-table-body { - overflow: scroll; + overflow: auto; height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined}; } @@ -161,11 +219,36 @@ const StyledTable: StyledComponent = styled(AntTable)` text-overflow: ellipsis; } + .ant-table-tbody > tr > td { + user-select: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 1px solid ${theme.colors.grayscale.light3}; + } + .ant-pagination-item-active { border-color: ${theme.colors.primary.base}; } - `} -`; + } +`, +); + +const StyledVirtualTable: StyledComponent = styled(VirtualTable)( + ({ theme }) => ` + .virtual-table .ant-table-container:before, + .virtual-table .ant-table-container:after { + display: none; + } + .virtual-table-cell { + box-sizing: border-box; + padding: ${theme.gridUnit * 4}px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`, +); const defaultLocale = { filterTitle: t('Filter menu'), @@ -188,6 +271,7 @@ const defaultLocale = { }; const selectionMap = {}; +const noop = () => {}; selectionMap[SelectionType.MULTI] = 'checkbox'; selectionMap[SelectionType.SINGLE] = 'radio'; selectionMap[SelectionType.DISABLED] = null; @@ -198,18 +282,22 @@ export function Table(props: TableProps) { columns, selectedRows = defaultRowSelection, handleRowSelection, - size, + size = TableSize.SMALL, selectionType = SelectionType.DISABLED, sticky = true, loading = false, resizable = false, reorderable = false, + usePagination = true, defaultPageSize = 15, pageSizeOptions = ['5', '15', '25', '50', '100'], hideData = false, emptyComponent, locale, - ...rest + height, + virtualize = false, + onChange = noop, + recordCount, } = props; const wrapperRef = useRef(null); @@ -292,32 +380,59 @@ export function Table(props: TableProps) { * The exclusion from the effect dependencies is intentional and should not be modified */ // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wrapperRef, reorderable, resizable, interactiveTableUtils]); + }, [wrapperRef, reorderable, resizable, virtualize, interactiveTableUtils]); const theme = useTheme(); + const paginationSettings: IPaginationOptions | false = usePagination + ? { + hideOnSinglePage: true, + pageSize, + pageSizeOptions, + onShowSizeChange: (page: number, size: number) => setPageSize(size), + } + : false; + + /** + * When recordCount is provided it lets the user of Table control total number of pages + * independent from data.length. This allows the parent component do things like server side paging + * where the user can be shown the total mount of data they can page through, but the component can provide + * data one page at a time, and respond to the onPageChange event to fetch and set new data + */ + if (paginationSettings && recordCount) { + paginationSettings.total = recordCount; + } + + const sharedProps = { + loading: { spinning: loading ?? false, indicator: }, + hasData: hideData ? false : data, + columns: derivedColumns, + dataSource: hideData ? [undefined] : data, + size, + pagination: paginationSettings, + locale: mergedLocale, + showSorterTooltip: false, + onChange, + theme, + height, + }; + return (
- }} - hasData={hideData ? false : data} - rowSelection={selectionTypeValue ? rowSelection : undefined} - columns={derivedColumns} - dataSource={hideData ? [undefined] : data} - size={size} - sticky={sticky} - pagination={{ - hideOnSinglePage: true, - pageSize, - pageSizeOptions, - onShowSizeChange: (page: number, size: number) => setPageSize(size), - }} - showSorterTooltip={false} - locale={mergedLocale} - theme={theme} - /> + {!virtualize && ( + + )} + {virtualize && ( + + )}
); diff --git a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx index 3f74e7781ed17..5b3fac1f94cf3 100644 --- a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx +++ b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx @@ -216,12 +216,8 @@ test('table multi select retain all the values selected', async () => { userEvent.click(tableSelect); - expect( - await screen.findByRole('option', { name: 'table_a' }), - ).toBeInTheDocument(); - act(() => { - const item = screen.getAllByText('table_a'); + const item = screen.getAllByText('table_b'); userEvent.click(item[item.length - 1]); }); @@ -230,17 +226,13 @@ test('table multi select retain all the values selected', async () => { userEvent.click(item[item.length - 1]); }); - const selectedValueContainer = getSelectItemContainer(tableSelect); + expect(screen.getByRole('option', { name: 'table_b' })).toHaveAttribute( + 'aria-selected', + 'true', + ); - expect(selectedValueContainer).toHaveLength(2); - expect( - await within(selectedValueContainer?.[0] as HTMLElement).findByText( - 'table_a', - ), - ).toBeInTheDocument(); - expect( - await within(selectedValueContainer?.[1] as HTMLElement).findByText( - 'table_c', - ), - ).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'table_c' })).toHaveAttribute( + 'aria-selected', + 'true', + ); }); diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx index 4a9fb62d944c0..2adf70c74c867 100644 --- a/superset-frontend/src/components/TableSelector/index.tsx +++ b/superset-frontend/src/components/TableSelector/index.tsx @@ -93,7 +93,7 @@ interface TableSelectorProps { database?: DatabaseObject | null; emptyState?: ReactNode; formMode?: boolean; - getDbList?: (arg0: any) => {}; + getDbList?: (arg0: any) => void; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: DatabaseObject) => void; diff --git a/superset-frontend/src/components/TableView/TableView.stories.tsx b/superset-frontend/src/components/TableView/TableView.stories.tsx index 9d28ca38b44d2..ff2079431a393 100644 --- a/superset-frontend/src/components/TableView/TableView.stories.tsx +++ b/superset-frontend/src/components/TableView/TableView.stories.tsx @@ -77,6 +77,7 @@ InteractiveTableView.args = { showRowCount: true, withPagination: true, columnsForWrapText: ['Summary'], + scrollTopOnPagination: false, }; InteractiveTableView.argTypes = { diff --git a/superset-frontend/src/components/TableView/TableView.tsx b/superset-frontend/src/components/TableView/TableView.tsx index 25a403ff9e60f..5bf393363669b 100644 --- a/superset-frontend/src/components/TableView/TableView.tsx +++ b/superset-frontend/src/components/TableView/TableView.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import isEqual from 'lodash/isEqual'; import { styled, t } from '@superset-ui/core'; import { useFilters, usePagination, useSortBy, useTable } from 'react-table'; @@ -49,6 +49,7 @@ export interface TableViewProps { isPaginationSticky?: boolean; showRowCount?: boolean; scrollTable?: boolean; + scrollTopOnPagination?: boolean; small?: boolean; columnsForWrapText?: string[]; } @@ -130,6 +131,7 @@ const TableView = ({ serverPagination = false, columnsForWrapText, onServerPagination = () => {}, + scrollTopOnPagination = false, ...props }: TableViewProps) => { const initialState = { @@ -161,22 +163,6 @@ const TableView = ({ useSortBy, usePagination, ); - useEffect(() => { - if (serverPagination && pageIndex !== initialState.pageIndex) { - onServerPagination({ - pageIndex, - }); - } - }, [pageIndex]); - - useEffect(() => { - if (serverPagination && !isEqual(sortBy, initialState.sortBy)) { - onServerPagination({ - pageIndex: 0, - sortBy, - }); - } - }, [sortBy]); const content = withPagination ? page : rows; @@ -194,10 +180,34 @@ const TableView = ({ const isEmpty = !loading && content.length === 0; const hasPagination = pageCount > 1 && withPagination; + const tableRef = useRef(null); + const handleGotoPage = (p: number) => { + if (scrollTopOnPagination) { + tableRef?.current?.scroll(0, 0); + } + gotoPage(p); + }; + + useEffect(() => { + if (serverPagination && pageIndex !== initialState.pageIndex) { + onServerPagination({ + pageIndex, + }); + } + }, [pageIndex]); + + useEffect(() => { + if (serverPagination && !isEqual(sortBy, initialState.sortBy)) { + onServerPagination({ + pageIndex: 0, + sortBy, + }); + } + }, [sortBy]); return ( <> - + gotoPage(p - 1)} + onChange={(p: number) => handleGotoPage(p - 1)} hideFirstAndLastPageLinks /> {showRowCount && ( diff --git a/superset-frontend/src/dashboard/actions/dashboardInfo.ts b/superset-frontend/src/dashboard/actions/dashboardInfo.ts index 19035a2b22033..dbec0cd1cc260 100644 --- a/superset-frontend/src/dashboard/actions/dashboardInfo.ts +++ b/superset-frontend/src/dashboard/actions/dashboardInfo.ts @@ -23,7 +23,7 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import { addDangerToast } from 'src/components/MessageToasts/actions'; import { DashboardInfo, - FilterBarLocation, + FilterBarOrientation, RootState, } from 'src/dashboard/types'; import { ChartConfiguration } from 'src/dashboard/reducers/types'; @@ -120,16 +120,18 @@ export const setChartConfiguration = } }; -export const SET_FILTER_BAR_LOCATION = 'SET_FILTER_BAR_LOCATION'; -export interface SetFilterBarLocation { - type: typeof SET_FILTER_BAR_LOCATION; - filterBarLocation: FilterBarLocation; +export const SET_FILTER_BAR_ORIENTATION = 'SET_FILTER_BAR_ORIENTATION'; +export interface SetFilterBarOrientation { + type: typeof SET_FILTER_BAR_ORIENTATION; + filterBarOrientation: FilterBarOrientation; } -export function setFilterBarLocation(filterBarLocation: FilterBarLocation) { - return { type: SET_FILTER_BAR_LOCATION, filterBarLocation }; +export function setFilterBarOrientation( + filterBarOrientation: FilterBarOrientation, +) { + return { type: SET_FILTER_BAR_ORIENTATION, filterBarOrientation }; } -export function saveFilterBarLocation(location: FilterBarLocation) { +export function saveFilterBarOrientation(orientation: FilterBarOrientation) { return async (dispatch: Dispatch, getState: () => RootState) => { const { id, metadata } = getState().dashboardInfo; const updateDashboard = makeApi< @@ -143,15 +145,15 @@ export function saveFilterBarLocation(location: FilterBarLocation) { const response = await updateDashboard({ json_metadata: JSON.stringify({ ...metadata, - filter_bar_location: location, + filter_bar_orientation: orientation, }), }); const updatedDashboard = response.result; const lastModifiedTime = response.last_modified_time; if (updatedDashboard.json_metadata) { const metadata = JSON.parse(updatedDashboard.json_metadata); - if (metadata.filter_bar_location) { - dispatch(setFilterBarLocation(metadata.filter_bar_location)); + if (metadata.filter_bar_orientation) { + dispatch(setFilterBarOrientation(metadata.filter_bar_orientation)); } } if (lastModifiedTime) { diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 5e3b97247a88c..ad0b38f4e7964 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -57,7 +57,7 @@ import getNativeFilterConfig from '../util/filterboxMigrationHelper'; import { updateColorSchema } from './dashboardInfo'; import { getChartIdsInFilterScope } from '../util/getChartIdsInFilterScope'; import updateComponentParentsList from '../util/updateComponentParentsList'; -import { FilterBarLocation } from '../types'; +import { FilterBarOrientation } from '../types'; export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD'; @@ -429,8 +429,8 @@ export const hydrateDashboard = flash_messages: common?.flash_messages, conf: common?.conf, }, - filterBarLocation: - metadata.filter_bar_location ?? FilterBarLocation.VERTICAL, + filterBarOrientation: + metadata.filter_bar_orientation ?? FilterBarOrientation.VERTICAL, }, dataMask, dashboardFilters, diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index cd0cfdb3fcd44..fdece196d09ce 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -40,7 +40,11 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex'; import { URL_PARAMS } from 'src/constants'; import { getUrlParam } from 'src/utils/urlUtils'; -import { DashboardLayout, RootState } from 'src/dashboard/types'; +import { + DashboardLayout, + FilterBarOrientation, + RootState, +} from 'src/dashboard/types'; import { setDirectPathToChild, setEditMode, @@ -241,6 +245,12 @@ const DashboardBuilder: FC = () => { const fullSizeChartId = useSelector( state => state.dashboardState.fullSizeChartId, ); + const filterBarOrientation = useSelector( + ({ dashboardInfo }) => + isFeatureEnabled(FeatureFlag.HORIZONTAL_FILTER_BAR) + ? dashboardInfo.filterBarOrientation + : FilterBarOrientation.VERTICAL, + ); const handleChangeTab = useCallback( ({ pathToTabIndex }: { pathToTabIndex: string[] }) => { @@ -277,6 +287,7 @@ const DashboardBuilder: FC = () => { uiConfig.hideTitle || standaloneMode === DashboardStandaloneMode.HIDE_NAV_AND_TITLE || isReport; + const [barTopOffset, setBarTopOffset] = useState(0); useEffect(() => { @@ -312,6 +323,7 @@ const DashboardBuilder: FC = () => { const filterSetEnabled = isFeatureEnabled( FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET, ); + const showFilterBar = nativeFiltersEnabled && !editMode; const offset = FILTER_BAR_HEADER_HEIGHT + @@ -354,6 +366,13 @@ const DashboardBuilder: FC = () => { ({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
{!hideDashboardHeader && } + {showFilterBar && + filterBarOrientation === FilterBarOrientation.HORIZONTAL && ( + + )} {dropIndicatorProps &&
} {!isReport && topLevelTabs && !uiConfig.hideNav && ( = () => {
), [ + directPathToChild, + nativeFiltersEnabled, + filterBarOrientation, editMode, handleChangeTab, handleDeleteTopLevelTabs, @@ -394,7 +416,7 @@ const DashboardBuilder: FC = () => { return ( - {nativeFiltersEnabled && !editMode && ( + {showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && ( <> = () => { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/ActionButtons.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/ActionButtons.test.tsx index 3c3f838c4ab08..525f519632463 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/ActionButtons.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/ActionButtons.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants'; import userEvent from '@testing-library/user-event'; import { render, screen } from 'spec/helpers/testing-library'; -import { ActionButtons } from './index'; +import ActionButtons from './index'; const createProps = () => ({ onApply: jest.fn(), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx index 5fca65c3ec6c1..a64f878c89b85 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx @@ -21,14 +21,15 @@ import { css, DataMaskState, DataMaskStateWithId, - styled, t, isDefined, + SupersetTheme, } from '@superset-ui/core'; import Button from 'src/components/Button'; import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants'; import { rgba } from 'emotion-rgba'; -import { getFilterBarTestId } from '../index'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import { getFilterBarTestId } from '../utils'; interface ActionButtonsProps { width?: number; @@ -37,61 +38,77 @@ interface ActionButtonsProps { dataMaskSelected: DataMaskState; dataMaskApplied: DataMaskStateWithId; isApplyDisabled: boolean; + filterBarOrientation?: FilterBarOrientation; } -const ActionButtonsContainer = styled.div<{ width: number }>` - ${({ theme, width }) => css` - display: flex; - flex-direction: column; - align-items: center; +const containerStyle = (theme: SupersetTheme) => css` + display: flex; - position: fixed; - z-index: 100; + && > .filter-clear-all-button { + color: ${theme.colors.grayscale.base}; + margin-left: 0; + &:hover { + color: ${theme.colors.primary.dark1}; + } - // filter bar width minus 1px for border - width: ${width - 1}px; - bottom: 0; + &[disabled], + &[disabled]:hover { + color: ${theme.colors.grayscale.light1}; + } + } +`; - padding: ${theme.gridUnit * 4}px; - padding-top: ${theme.gridUnit * 6}px; +const verticalStyle = (theme: SupersetTheme, width: number) => css` + flex-direction: column; + align-items: center; + pointer-events: none; + position: fixed; + z-index: 100; - background: linear-gradient( - ${rgba(theme.colors.grayscale.light5, 0)}, - ${theme.colors.grayscale.light5} ${theme.opacity.mediumLight} - ); + // filter bar width minus 1px for border + width: ${width - 1}px; + bottom: 0; - pointer-events: none; + padding: ${theme.gridUnit * 4}px; + padding-top: ${theme.gridUnit * 6}px; - & > button { - pointer-events: auto; - } + background: linear-gradient( + ${rgba(theme.colors.grayscale.light5, 0)}, + ${theme.colors.grayscale.light5} ${theme.opacity.mediumLight} + ); - & > .filter-apply-button { - margin-bottom: ${theme.gridUnit * 3}px; - } + & > button { + pointer-events: auto; + } - && > .filter-clear-all-button { - color: ${theme.colors.grayscale.base}; - margin-left: 0; - &:hover { - color: ${theme.colors.primary.dark1}; - } + & > .filter-apply-button { + margin-bottom: ${theme.gridUnit * 3}px; + } +`; - &[disabled], - &[disabled]:hover { - color: ${theme.colors.grayscale.light1}; - } +const horizontalStyle = (theme: SupersetTheme) => css` + margin: 0 ${theme.gridUnit * 4}px; + && > .filter-clear-all-button { + text-transform: capitalize; + font-weight: ${theme.typography.weights.normal}; + } + & > .filter-apply-button { + &[disabled], + &[disabled]:hover { + color: ${theme.colors.grayscale.light1}; + background: ${theme.colors.grayscale.light3}; } - `}; + } `; -export const ActionButtons = ({ +const ActionButtons = ({ width = OPEN_FILTER_BAR_WIDTH, onApply, onClearAll, dataMaskApplied, dataMaskSelected, isApplyDisabled, + filterBarOrientation = FilterBarOrientation.VERTICAL, }: ActionButtonsProps) => { const isClearAllEnabled = useMemo( () => @@ -103,9 +120,16 @@ export const ActionButtons = ({ ), [dataMaskApplied, dataMaskSelected], ); + const isVertical = filterBarOrientation === FilterBarOrientation.VERTICAL; return ( - +
[ + containerStyle(theme), + isVertical ? verticalStyle(theme, width) : horizontalStyle(theme), + ]} + data-test="filterbar-action-buttons" + > - +
); }; + +export default ActionButtons; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx index 871f2b402647f..7a8106ecbe971 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx @@ -29,7 +29,9 @@ import { TimeFilterPlugin, SelectFilterPlugin } from 'src/filters/components'; import { DATE_FILTER_TEST_KEY } from 'src/explore/components/controls/DateFilterControl'; import fetchMock from 'fetch-mock'; import { waitFor } from '@testing-library/react'; -import FilterBar, { FILTER_BAR_TEST_ID } from '.'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import { FILTER_BAR_TEST_ID } from './utils'; +import FilterBar from '.'; import { FILTERS_CONFIG_MODAL_TEST_ID } from '../FiltersConfigModal/FiltersConfigModal'; jest.useFakeTimers(); @@ -216,12 +218,23 @@ describe('FilterBar', () => { }); const renderWrapper = (props = closedBarProps, state?: object) => - render(, { - initialState: state, - useDnd: true, - useRedux: true, - useRouter: true, - }); + render( + , + { + initialState: state, + useDnd: true, + useRedux: true, + useRouter: true, + }, + ); it('should render', () => { const { container } = renderWrapper(); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/FilterBarLocationSelect.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx similarity index 93% rename from superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/FilterBarLocationSelect.test.tsx rename to superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx index 90b640a2c1678..28a40aad057c6 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/FilterBarLocationSelect.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx @@ -22,9 +22,9 @@ import fetchMock from 'fetch-mock'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render, screen, within } from 'spec/helpers/testing-library'; -import { DashboardInfo, FilterBarLocation } from 'src/dashboard/types'; +import { DashboardInfo, FilterBarOrientation } from 'src/dashboard/types'; import * as mockedMessageActions from 'src/components/MessageToasts/actions'; -import { FilterBarLocationSelect } from './index'; +import FilterBarOrientationSelect from '.'; const initialState: { dashboardInfo: DashboardInfo } = { dashboardInfo: { @@ -42,7 +42,7 @@ const initialState: { dashboardInfo: DashboardInfo } = { }, json_metadata: '', dash_edit_perm: true, - filterBarLocation: FilterBarLocation.VERTICAL, + filterBarOrientation: FilterBarOrientation.VERTICAL, common: { conf: {}, flash_messages: [], @@ -51,7 +51,7 @@ const initialState: { dashboardInfo: DashboardInfo } = { }; const setup = (dashboardInfoOverride: Partial = {}) => - render(, { + render(, { useRedux: true, initialState: { ...initialState, @@ -78,7 +78,7 @@ test('Popover opens with "Vertical" selected', async () => { }); test('Popover opens with "Horizontal" selected', async () => { - setup({ filterBarLocation: FilterBarLocation.HORIZONTAL }); + setup({ filterBarOrientation: FilterBarOrientation.HORIZONTAL }); userEvent.click(screen.getByLabelText('gear')); expect(await screen.findByText('Vertical (Left)')).toBeInTheDocument(); expect(screen.getByText('Horizontal (Top)')).toBeInTheDocument(); @@ -93,7 +93,7 @@ test('On selection change, send request and update checked value', async () => { result: { json_metadata: JSON.stringify({ ...initialState.dashboardInfo.metadata, - filter_bar_location: 'HORIZONTAL', + filter_bar_orientation: 'HORIZONTAL', }), }, }); @@ -124,7 +124,7 @@ test('On selection change, send request and update checked value', async () => { JSON.stringify({ json_metadata: JSON.stringify({ ...initialState.dashboardInfo.metadata, - filter_bar_location: 'HORIZONTAL', + filter_bar_orientation: 'HORIZONTAL', }), }), ), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx similarity index 63% rename from superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/index.tsx rename to superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx index 82d4ba92e6ec0..70d7075c6e60f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarLocationSelect/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx @@ -21,60 +21,62 @@ import React, { useCallback, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { t, useTheme } from '@superset-ui/core'; import { MenuProps } from 'src/components/Menu'; -import { FilterBarLocation, RootState } from 'src/dashboard/types'; -import { saveFilterBarLocation } from 'src/dashboard/actions/dashboardInfo'; +import { FilterBarOrientation, RootState } from 'src/dashboard/types'; +import { saveFilterBarOrientation } from 'src/dashboard/actions/dashboardInfo'; import Icons from 'src/components/Icons'; import DropdownSelectableIcon from 'src/components/DropdownSelectableIcon'; -export const FilterBarLocationSelect = () => { +const FilterBarOrientationSelect = () => { const dispatch = useDispatch(); const theme = useTheme(); - const filterBarLocation = useSelector( - ({ dashboardInfo }) => dashboardInfo.filterBarLocation, + const filterBarOrientation = useSelector( + ({ dashboardInfo }) => dashboardInfo.filterBarOrientation, ); - const [selectedFilterBarLocation, setSelectedFilterBarLocation] = - useState(filterBarLocation); + const [selectedFilterBarOrientation, setSelectedFilterBarOrientation] = + useState(filterBarOrientation); - const toggleFilterBarLocation = useCallback( + const toggleFilterBarOrientation = useCallback( async ( selection: Parameters< Required>['onSelect'] >[0], ) => { - const selectedKey = selection.key as FilterBarLocation; - if (selectedKey !== filterBarLocation) { + const selectedKey = selection.key as FilterBarOrientation; + if (selectedKey !== filterBarOrientation) { // set displayed selection in local state for immediate visual response after clicking - setSelectedFilterBarLocation(selectedKey); + setSelectedFilterBarOrientation(selectedKey); try { // save selection in Redux and backend await dispatch( - saveFilterBarLocation(selection.key as FilterBarLocation), + saveFilterBarOrientation(selection.key as FilterBarOrientation), ); } catch { // revert local state in case of error when saving - setSelectedFilterBarLocation(filterBarLocation); + setSelectedFilterBarOrientation(filterBarOrientation); } } }, - [dispatch, filterBarLocation], + [dispatch, filterBarOrientation], ); return ( } menuItems={[ { - key: FilterBarLocation.VERTICAL, + key: FilterBarOrientation.VERTICAL, label: t('Vertical (Left)'), }, { - key: FilterBarLocation.HORIZONTAL, + key: FilterBarOrientation.HORIZONTAL, label: t('Horizontal (Top)'), }, ]} - selectedKeys={[selectedFilterBarLocation]} + selectedKeys={[selectedFilterBarOrientation]} /> ); }; + +export default FilterBarOrientationSelect; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx index e99171c7e839c..8319c3c9fb480 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx @@ -22,7 +22,7 @@ import { setFilterConfiguration } from 'src/dashboard/actions/nativeFilters'; import Button from 'src/components/Button'; import { FilterConfiguration, styled } from '@superset-ui/core'; import FiltersConfigModal from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal'; -import { getFilterBarTestId } from '..'; +import { getFilterBarTestId } from '../utils'; export interface FCBProps { createNewOnOpen?: boolean; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx index 986572c7f0cbd..29cdf9d460b1f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx @@ -20,25 +20,40 @@ import React, { useContext, useMemo, useState } from 'react'; import { styled, SupersetTheme } from '@superset-ui/core'; import { FormItem as StyledFormItem, Form } from 'src/components/Form'; import { Tooltip } from 'src/components/Tooltip'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import { truncationCSS } from 'src/hooks/useTruncation'; import { checkIsMissingRequiredValue } from '../utils'; import FilterValue from './FilterValue'; -import { FilterProps } from './types'; import { FilterCard } from '../../FilterCard'; -import { FilterBarScrollContext } from '../index'; +import { FilterBarScrollContext } from '../Vertical'; +import { FilterControlProps } from './types'; +import { FilterCardPlacement } from '../../FilterCard/types'; const StyledIcon = styled.div` position: absolute; right: 0; `; -const StyledFilterControlTitle = styled.h4` +const VerticalFilterControlTitle = styled.h4` font-size: ${({ theme }) => theme.typography.sizes.s}px; color: ${({ theme }) => theme.colors.grayscale.dark1}; margin: 0; overflow-wrap: break-word; `; -const StyledFilterControlTitleBox = styled.div` +const HorizontalFilterControlTitle = styled(VerticalFilterControlTitle)` + font-weight: ${({ theme }) => theme.typography.weights.normal}; + color: ${({ theme }) => theme.colors.grayscale.base}; + ${truncationCSS} +`; + +const HorizontalOverflowFilterControlTitle = styled( + HorizontalFilterControlTitle, +)` + max-width: none; +`; + +const VerticalFilterControlTitleBox = styled.div` display: flex; flex-direction: row; align-items: center; @@ -46,7 +61,18 @@ const StyledFilterControlTitleBox = styled.div` margin-bottom: ${({ theme }) => theme.gridUnit}px; `; -const StyledFilterControlContainer = styled(Form)` +const HorizontalFilterControlTitleBox = styled(VerticalFilterControlTitleBox)` + margin-bottom: unset; + max-width: ${({ theme }) => theme.gridUnit * 15}px; +`; + +const HorizontalOverflowFilterControlTitleBox = styled( + VerticalFilterControlTitleBox, +)` + width: 100%; +`; + +const VerticalFilterControlContainer = styled(Form)` width: 100%; && .ant-form-item-label > label { text-transform: none; @@ -58,7 +84,25 @@ const StyledFilterControlContainer = styled(Form)` } `; -const FormItem = styled(StyledFormItem)` +const HorizontalFilterControlContainer = styled(Form)` + && .ant-form-item-label > label { + margin-bottom: 0; + text-transform: none; + } + .ant-form-item-tooltip { + margin-bottom: ${({ theme }) => theme.gridUnit}px; + } +`; + +const HorizontalOverflowFilterControlContainer = styled( + VerticalFilterControlContainer, +)` + && .ant-form-item-label > label { + padding-right: unset; + } +`; + +const VerticalFormItem = styled(StyledFormItem)` .ant-form-item-label { label.ant-form-item-required:not(.ant-form-item-required-mark-optional) { &::after { @@ -68,6 +112,62 @@ const FormItem = styled(StyledFormItem)` } `; +const HorizontalFormItem = styled(StyledFormItem)` + && { + margin-bottom: 0; + align-items: center; + } + + .ant-form-item-label { + padding-bottom: 0; + margin-right: ${({ theme }) => theme.gridUnit * 2}px; + label.ant-form-item-required:not(.ant-form-item-required-mark-optional) { + &::after { + display: none; + } + } + + & > label::after { + display: none; + } + } + + .ant-form-item-control { + width: ${({ theme }) => theme.gridUnit * 40}px; + } +`; + +const HorizontalOverflowFormItem = VerticalFormItem; + +const useFilterControlDisplay = ( + orientation: FilterBarOrientation, + overflow: boolean, +) => + useMemo(() => { + if (orientation === FilterBarOrientation.HORIZONTAL) { + if (overflow) { + return { + FilterControlContainer: HorizontalOverflowFilterControlContainer, + FormItem: HorizontalOverflowFormItem, + FilterControlTitleBox: HorizontalOverflowFilterControlTitleBox, + FilterControlTitle: HorizontalOverflowFilterControlTitle, + }; + } + return { + FilterControlContainer: HorizontalFilterControlContainer, + FormItem: HorizontalFormItem, + FilterControlTitleBox: HorizontalFilterControlTitleBox, + FilterControlTitle: HorizontalFilterControlTitle, + }; + } + return { + FilterControlContainer: VerticalFilterControlContainer, + FormItem: VerticalFormItem, + FilterControlTitleBox: VerticalFilterControlTitleBox, + FilterControlTitle: VerticalFilterControlTitle, + }; + }, [orientation, overflow]); + const ToolTipContainer = styled.div` font-size: ${({ theme }) => theme.typography.sizes.m}px; display: flex; @@ -109,7 +209,7 @@ const DescriptionToolTip = ({ description }: { description: string }) => ( ); -const FilterControl: React.FC = ({ +const FilterControl = ({ dataMaskSelected, filter, icon, @@ -118,7 +218,9 @@ const FilterControl: React.FC = ({ inView, showOverflow, parentRef, -}) => { + orientation = FilterBarOrientation.VERTICAL, + overflow = false, +}: FilterControlProps) => { const [isFilterActive, setIsFilterActive] = useState(false); const { name = '' } = filter; @@ -129,27 +231,60 @@ const FilterControl: React.FC = ({ ); const isRequired = !!filter.controlValues?.enableEmptyFilter; + const { + FilterControlContainer, + FormItem, + FilterControlTitleBox, + FilterControlTitle, + } = useFilterControlDisplay(orientation, overflow); + const label = useMemo( () => ( - - + + {name} - + {isRequired && } {filter.description?.trim() && ( )} {icon} - + ), - [name, isRequired, filter.description, icon], + [ + FilterControlTitleBox, + FilterControlTitle, + name, + isRequired, + filter.description, + icon, + ], ); const isScrolling = useContext(FilterBarScrollContext); + const filterCardPlacement = useMemo(() => { + if (orientation === FilterBarOrientation.HORIZONTAL) { + if (overflow) { + return FilterCardPlacement.Left; + } + return FilterCardPlacement.Bottom; + } + return FilterCardPlacement.Right; + }, [orientation, overflow]); return ( - - + +
= ({ inView={inView} parentRef={parentRef} setFilterActive={setIsFilterActive} + orientation={orientation} + overflow={overflow} />
-
+ ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx index 79085daee3f48..0da90351fc6ee 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx @@ -16,34 +16,35 @@ * specific language governing permissions and limitations * under the License. */ -import React, { FC, useCallback, useMemo } from 'react'; -import { css } from '@emotion/react'; +import React, { FC, useCallback, useMemo, useState } from 'react'; import { DataMask, DataMaskStateWithId, Filter, - isFilterDivider, - styled, + Divider, + css, + SupersetTheme, t, + isNativeFilter, + isFeatureEnabled, + FeatureFlag, } from '@superset-ui/core'; import { createHtmlPortalNode, InPortal, OutPortal, } from 'react-reverse-portal'; -import { AntdCollapse } from 'src/components'; +import { useSelector } from 'react-redux'; import { useDashboardHasTabs, useSelectFiltersInScope, } from 'src/dashboard/components/nativeFilters/state'; -import { useFilters } from '../state'; -import FilterControl from './FilterControl'; - -const Wrapper = styled.div` - padding: ${({ theme }) => theme.gridUnit * 4}px; - // 108px padding to make room for buttons with position: absolute - padding-bottom: ${({ theme }) => theme.gridUnit * 27}px; -`; +import { FilterBarOrientation, RootState } from 'src/dashboard/types'; +import DropdownContainer from 'src/components/DropdownContainer'; +import Icons from 'src/components/Icons'; +import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible'; +import { useFilterControlFactory } from '../useFilterControlFactory'; +import { FiltersDropdownContent } from '../FiltersDropdownContent'; type FilterControlsProps = { directPathToChild?: string[]; @@ -56,112 +57,169 @@ const FilterControls: FC = ({ dataMaskSelected, onFilterSelectionChange, }) => { - const filters = useFilters(); - const filterValues = useMemo(() => Object.values(filters), [filters]); + const filterBarOrientation = useSelector( + ({ dashboardInfo }) => + isFeatureEnabled(FeatureFlag.HORIZONTAL_FILTER_BAR) + ? dashboardInfo.filterBarOrientation + : FilterBarOrientation.VERTICAL, + ); + + const [overflowedIds, setOverflowedIds] = useState([]); + + const { filterControlFactory, filtersWithValues } = useFilterControlFactory( + dataMaskSelected, + directPathToChild, + onFilterSelectionChange, + ); const portalNodes = useMemo(() => { - const nodes = new Array(filterValues.length); - for (let i = 0; i < filterValues.length; i += 1) { + const nodes = new Array(filtersWithValues.length); + for (let i = 0; i < filtersWithValues.length; i += 1) { nodes[i] = createHtmlPortalNode(); } return nodes; - }, [filterValues.length]); + }, [filtersWithValues.length]); - const filtersWithValues = useMemo( - () => - filterValues.map(filter => ({ - ...filter, - dataMask: dataMaskSelected[filter.id], - })), - [filterValues, dataMaskSelected], - ); const filterIds = new Set(filtersWithValues.map(item => item.id)); const [filtersInScope, filtersOutOfScope] = useSelectFiltersInScope(filtersWithValues); + const dashboardHasTabs = useDashboardHasTabs(); const showCollapsePanel = dashboardHasTabs && filtersWithValues.length > 0; - const filterControlFactory = useCallback( - index => { - const filter = filtersWithValues[index]; - if (isFilterDivider(filter)) { - return ( -
-

{filter.title}

-

{filter.description}

+ const renderer = useCallback( + ({ id }: Filter | Divider) => { + const index = filtersWithValues.findIndex(f => f.id === id); + return ; + }, + [filtersWithValues, portalNodes], + ); + + const renderVerticalContent = () => ( + <> + {filtersInScope.map(renderer)} + {showCollapsePanel && ( + 0} + renderer={renderer} + /> + )} + + ); + + const items = useMemo( + () => + filtersInScope.map(filter => ({ + id: filter.id, + element: ( +
+ {renderer(filter)}
- ); + ), + })), + [filtersInScope, renderer], + ); + + const overflowedFiltersInScope = useMemo( + () => filtersInScope.filter(({ id }) => overflowedIds?.includes(id)), + [filtersInScope, overflowedIds], + ); + + const activeOverflowedFiltersInScope = useMemo( + () => + overflowedFiltersInScope.filter( + filter => isNativeFilter(filter) && filter.dataMask?.filterState?.value, + ).length, + [overflowedFiltersInScope], + ); + + const renderHorizontalContent = () => ( +
+ css` + padding-left: ${theme.gridUnit * 4}px; + min-width: 0; + ` } - return ( - - ); - }, - [ - filtersWithValues, - JSON.stringify(dataMaskSelected), - directPathToChild, - onFilterSelectionChange, - ], + > + + } + dropdownTriggerText={t('More filters')} + dropdownTriggerCount={activeOverflowedFiltersInScope} + dropdownContent={ + overflowedFiltersInScope.length || + (filtersOutOfScope.length && showCollapsePanel) + ? () => ( + + ) + : undefined + } + onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => { + if ( + nextOverflowedIds.length !== overflowedIds.length || + overflowedIds.reduce( + (a, b, i) => a || b !== nextOverflowedIds[i], + false, + ) + ) { + setOverflowedIds(nextOverflowedIds); + } + }} + /> +
); + + const overflowedByIndex = useMemo(() => { + const filtersOutOfScopeIds = new Set(filtersOutOfScope.map(({ id }) => id)); + const overflowedFiltersInScopeIds = new Set( + overflowedFiltersInScope.map(({ id }) => id), + ); + + return filtersWithValues.map( + filter => + filtersOutOfScopeIds.has(filter.id) || + overflowedFiltersInScopeIds.has(filter.id), + ); + }, [filtersOutOfScope, filtersWithValues, overflowedFiltersInScope]); + return ( - + <> {portalNodes - .filter((node, index) => filterIds.has(filterValues[index].id)) + .filter((node, index) => filterIds.has(filtersWithValues[index].id)) .map((node, index) => ( - {filterControlFactory(index)} + + {filterControlFactory( + index, + filterBarOrientation, + overflowedByIndex[index], + )} + ))} - {filtersInScope.map(filter => { - const index = filterValues.findIndex(f => f.id === filter.id); - return ; - })} - {showCollapsePanel && ( - css` - &.ant-collapse { - margin-top: ${filtersInScope.length > 0 - ? theme.gridUnit * 6 - : 0}px; - & > .ant-collapse-item { - & > .ant-collapse-header { - padding-left: 0; - padding-bottom: ${theme.gridUnit * 2}px; - - & > .ant-collapse-arrow { - right: ${theme.gridUnit}px; - } - } - - & .ant-collapse-content-box { - padding: ${theme.gridUnit * 4}px 0 0; - } - } - } - `} - > - - {filtersOutOfScope.map(filter => { - const index = filtersWithValues.findIndex( - f => f.id === filter.id, - ); - return ; - })} - - - )} - + {filterBarOrientation === FilterBarOrientation.VERTICAL && + renderVerticalContent()} + {filterBarOrientation === FilterBarOrientation.HORIZONTAL && + renderHorizontalContent()} + ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx new file mode 100644 index 0000000000000..212e9033588f3 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx @@ -0,0 +1,122 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import FilterDivider from './FilterDivider'; +import 'src/dashboard/stylesheets/index.less'; +import { FilterDividerProps } from './types'; + +export default { + title: 'FilterDivider', + component: FilterDivider, +}; + +export const VerticalFilterDivider = (props: FilterDividerProps) => ( +
+
+ +
+
+); + +export const HorizontalFilterDivider = (props: FilterDividerProps) => ( +
+
+ +
+
+); + +export const HorizontalOverflowFilterDivider = (props: FilterDividerProps) => ( +
+
+ +
+
+); + +const args = { + title: 'Sample title', + description: 'Sample description', +}; + +const story = { parameters: { knobs: { disable: true } } }; + +VerticalFilterDivider.args = { + ...args, + horizontal: false, + overflow: false, +}; + +VerticalFilterDivider.story = story; + +HorizontalFilterDivider.args = { + ...args, + horizontal: true, + overflow: false, +}; + +HorizontalFilterDivider.story = story; + +HorizontalOverflowFilterDivider.args = { + ...args, + horizontal: true, + overflow: true, +}; + +HorizontalOverflowFilterDivider.story = story; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.test.tsx new file mode 100644 index 0000000000000..8491c93d8fa14 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.test.tsx @@ -0,0 +1,135 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import FilterDivider from './FilterDivider'; + +const SAMPLE_TITLE = 'Sample title'; +const SAMPLE_DESCRIPTION = + 'Sample description that is even longer, it goes on and on and on and on and on and on and on and on and on and on.'; + +test('vertical mode, title', () => { + render(); + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.queryByTestId('divider-description'); + expect(description).not.toBeInTheDocument(); + const descriptionIcon = screen.queryByTestId('divider-description-icon'); + expect(descriptionIcon).not.toBeInTheDocument(); +}); + +test('vertical mode, title and description', () => { + render( + , + ); + + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.getByTestId('divider-description'); + expect(description).toBeVisible(); + expect(description).toHaveTextContent(SAMPLE_DESCRIPTION); + const descriptionIcon = screen.queryByTestId('divider-description-icon'); + expect(descriptionIcon).not.toBeInTheDocument(); +}); + +test('horizontal mode, title', () => { + render( + , + ); + + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.queryByTestId('divider-description'); + expect(description).not.toBeInTheDocument(); + const descriptionIcon = screen.queryByTestId('divider-description-icon'); + expect(descriptionIcon).not.toBeInTheDocument(); +}); + +test('horizontal mode, title and description', async () => { + render( + , + ); + + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.queryByTestId('divider-description'); + expect(description).not.toBeInTheDocument(); + const descriptionIcon = screen.getByTestId('divider-description-icon'); + expect(descriptionIcon).toBeVisible(); + userEvent.hover(descriptionIcon); + const tooltip = await screen.findByRole('tooltip'); + + expect(tooltip).toBeInTheDocument(); + expect(tooltip).toHaveTextContent(SAMPLE_DESCRIPTION); +}); + +test('horizontal overflow mode, title', () => { + render( + , + ); + + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.queryByTestId('divider-description'); + expect(description).not.toBeInTheDocument(); + const descriptionIcon = screen.queryByTestId('divider-description-icon'); + expect(descriptionIcon).not.toBeInTheDocument(); +}); + +test('horizontal overflow mode, title and description', () => { + render( + , + ); + + const title = screen.getByRole('heading', { name: SAMPLE_TITLE }); + expect(title).toBeVisible(); + expect(title).toHaveTextContent(SAMPLE_TITLE); + const description = screen.queryByTestId('divider-description'); + expect(description).toBeVisible(); + expect(description).toHaveTextContent(SAMPLE_DESCRIPTION); + const descriptionIcon = screen.queryByTestId('divider-description-icon'); + expect(descriptionIcon).not.toBeInTheDocument(); +}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.tsx new file mode 100644 index 0000000000000..4cdeed54c0fae --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.tsx @@ -0,0 +1,166 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { css, useTheme } from '@superset-ui/core'; +import React from 'react'; +import Icons from 'src/components/Icons'; +import { Tooltip } from 'src/components/Tooltip'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import { useCSSTextTruncation, truncationCSS } from 'src/hooks/useTruncation'; +import { FilterDividerProps } from './types'; + +const VerticalDivider = ({ title, description }: FilterDividerProps) => ( +
+

{title}

+ {description ?

{description}

: null} +
+); + +const HorizontalDivider = ({ title, description }: FilterDividerProps) => { + const theme = useTheme(); + const [titleRef, titleIsTruncated] = + useCSSTextTruncation(); + + const tooltipOverlay = ( + <> + {titleIsTruncated ? ( +
+ {title} +
+ ) : null} + {description ?
{description}
: null} + + ); + + return ( +
+

+ {title} +

+ {titleIsTruncated || description ? ( + + + + ) : null} +
+ ); +}; + +const HorizontalOverflowDivider = ({ + title, + description, +}: FilterDividerProps) => { + const theme = useTheme(); + const [titleRef, titleIsTruncated] = + useCSSTextTruncation(); + + const [descriptionRef, descriptionIsTruncated] = + useCSSTextTruncation(); + + return ( +
+ {title} : null}> +

+ {title} +

+
+ {description ? ( + +

+ {description} +

+
+ ) : null} +
+ ); +}; + +const FilterDivider = ({ + title, + description, + orientation = FilterBarOrientation.VERTICAL, + overflow = false, +}: FilterDividerProps) => { + if (orientation === FilterBarOrientation.HORIZONTAL) { + if (overflow) { + return ( + + ); + } + + return ; + } + + return ; +}; + +export default FilterDivider; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx index 4337d59ed86fc..a08200d83b217 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx @@ -42,10 +42,10 @@ import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { waitForAsyncData } from 'src/middleware/asyncEvent'; import { ClientErrorObject } from 'src/utils/getClientErrorObject'; -import { RootState } from 'src/dashboard/types'; +import { FilterBarOrientation, RootState } from 'src/dashboard/types'; import { onFiltersRefreshSuccess } from 'src/dashboard/actions/dashboardState'; import { dispatchFocusAction } from './utils'; -import { FilterProps } from './types'; +import { FilterControlProps } from './types'; import { getFormData } from '../../utils'; import { useFilterDependencies } from './state'; import { checkIsMissingRequiredValue } from '../utils'; @@ -75,7 +75,7 @@ const useShouldFilterRefresh = () => { return !isDashboardRefreshing && isFilterRefreshing; }; -const FilterValue: React.FC = ({ +const FilterValue: React.FC = ({ dataMaskSelected, filter, directPathToChild, @@ -84,6 +84,8 @@ const FilterValue: React.FC = ({ showOverflow, parentRef, setFilterActive, + orientation = FilterBarOrientation.VERTICAL, + overflow = false, }) => { const { id, targets, filterType, adhoc_filters, time_range } = filter; const metadata = getChartMetadataRegistry().get(filterType); @@ -251,6 +253,11 @@ const FilterValue: React.FC = ({ [filter.dataMask?.filterState, isMissingRequiredValue], ); + const formDataWithDisplayParams = useMemo( + () => ({ ...formData, orientation, overflow }), + [formData, orientation, overflow], + ); + if (error) { return ( = ({ height={HEIGHT} width="100%" showOverflow={showOverflow} - formData={formData} + formData={formDataWithDisplayParams} parentRef={parentRef} inputRef={inputRef} // For charts that don't have datasource we need workaround for empty placeholder diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts index e5b553712634e..a48ca5f0aab6f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts @@ -18,8 +18,19 @@ */ import React, { RefObject } from 'react'; import { DataMask, DataMaskStateWithId, Filter } from '@superset-ui/core'; +import { FilterBarOrientation } from 'src/dashboard/types'; -export interface FilterProps { +export interface BaseFilterProps { + orientation?: FilterBarOrientation; + overflow?: boolean; +} + +export interface FilterDividerProps extends BaseFilterProps { + title: string; + description: string; +} + +export interface FilterControlProps extends BaseFilterProps { dataMaskSelected?: DataMaskStateWithId; filter: Filter & { dataMask?: DataMask; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx index 689eaa826627d..c4807d751fd11 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx @@ -27,7 +27,7 @@ import { ActionButtons } from './Footer'; import { useNativeFiltersDataMask, useFilters, useFilterSets } from '../state'; import { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils'; import { useFilterSetNameDuplicated } from './state'; -import { getFilterBarTestId } from '../index'; +import { getFilterBarTestId } from '../utils'; const Wrapper = styled.div` display: grid; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx index 53ea1c94c5289..14e0c88b7ed9c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx @@ -31,7 +31,7 @@ import { CheckOutlined, EllipsisOutlined } from '@ant-design/icons'; import Button from 'src/components/Button'; import { Tooltip } from 'src/components/Tooltip'; import FiltersHeader from './FiltersHeader'; -import { getFilterBarTestId } from '..'; +import { getFilterBarTestId } from '../utils'; const HeaderButton = styled(Button)` padding: 0; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.test.tsx index 78ad4599933d3..2fe855147ab60 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.test.tsx @@ -21,7 +21,7 @@ import { render, screen } from 'spec/helpers/testing-library'; import { mockStore } from 'spec/fixtures/mockStore'; import { Provider } from 'react-redux'; import FilterSets, { FilterSetsProps } from '.'; -import { TabIds } from '../utils'; +import { TabIds } from '../types'; const createProps = () => ({ disabled: false, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx index 5a7bff6527f37..5982bf515a6eb 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx @@ -30,7 +30,7 @@ import Icons from 'src/components/Icons'; import { areObjectsEqual } from 'src/reduxUtils'; import { getFilterValueForDisplay } from './utils'; import { useFilters } from '../state'; -import { getFilterBarTestId } from '../index'; +import { getFilterBarTestId } from '../utils'; const FilterHeader = styled.div` display: flex; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx index df6c6ee44ebf0..7847927ff5b86 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx @@ -22,7 +22,7 @@ import Button from 'src/components/Button'; import { Tooltip } from 'src/components/Tooltip'; import { APPLY_FILTERS_HINT } from './utils'; import { useFilterSetNameDuplicated } from './state'; -import { getFilterBarTestId } from '..'; +import { getFilterBarTestId } from '../utils'; export type FooterProps = { filterSetName: string; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx index 5fdfd481719ea..2e12425de75b8 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx @@ -40,8 +40,8 @@ import { findExistingFilterSet } from './utils'; import { useFilters, useNativeFiltersDataMask, useFilterSets } from '../state'; import Footer from './Footer'; import FilterSetUnit from './FilterSetUnit'; -import { getFilterBarTestId } from '..'; -import { TabIds } from '../utils'; +import { getFilterBarTestId } from '../utils'; +import { TabIds } from '../types'; const FilterSetsWrapper = styled.div` display: grid; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersDropdownContent/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersDropdownContent/index.tsx new file mode 100644 index 0000000000000..7ed19f44c670a --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersDropdownContent/index.tsx @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { ReactNode } from 'react'; +import { css, Divider, Filter, SupersetTheme } from '@superset-ui/core'; +import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible'; + +export interface FiltersDropdownContentProps { + filtersInScope: (Filter | Divider)[]; + filtersOutOfScope: (Filter | Divider)[]; + renderer: (filter: Filter | Divider) => ReactNode; + showCollapsePanel?: boolean; +} + +export const FiltersDropdownContent = ({ + filtersInScope, + filtersOutOfScope, + renderer, + showCollapsePanel, +}: FiltersDropdownContentProps) => ( +
+ css` + width: ${theme.gridUnit * 56}px; + ` + } + > + {filtersInScope.map(renderer)} + {showCollapsePanel && ( + + )} +
+); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersOutOfScopeCollapsible/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersOutOfScopeCollapsible/index.tsx new file mode 100644 index 0000000000000..aee46f8d61569 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FiltersOutOfScopeCollapsible/index.tsx @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { ReactNode } from 'react'; +import { css } from '@emotion/react'; +import { Divider, Filter, t } from '@superset-ui/core'; +import { AntdCollapse } from 'src/components'; + +export interface FiltersOutOfScopeCollapsibleProps { + filtersOutOfScope: (Filter | Divider)[]; + renderer: (filter: Filter | Divider) => ReactNode; + hasTopMargin?: boolean; +} + +export const FiltersOutOfScopeCollapsible = ({ + filtersOutOfScope, + hasTopMargin, + renderer, +}: FiltersOutOfScopeCollapsibleProps) => ( + css` + &.ant-collapse { + margin-top: ${hasTopMargin + ? theme.gridUnit * 6 + : theme.gridUnit * -3}px; + & > .ant-collapse-item { + & > .ant-collapse-header { + padding-left: 0; + padding-bottom: ${theme.gridUnit * 2}px; + + & > .ant-collapse-arrow { + right: ${theme.gridUnit}px; + } + } + + & .ant-collapse-content-box { + padding: ${theme.gridUnit * 4}px 0 0; + } + } + } + `} + > + + {filtersOutOfScope.map(renderer)} + + +); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx index a20d6a61f75c2..5057bef9b143b 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx @@ -32,8 +32,8 @@ import { useSelector } from 'react-redux'; import FilterConfigurationLink from 'src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink'; import { useFilters } from 'src/dashboard/components/nativeFilters/FilterBar/state'; import { RootState } from 'src/dashboard/types'; -import { getFilterBarTestId } from '..'; -import { FilterBarLocationSelect } from '../FilterBarLocationSelect'; +import { getFilterBarTestId } from '../utils'; +import FilterBarOrientationSelect from '../FilterBarOrientationSelect'; const TitleArea = styled.h4` display: flex; @@ -56,8 +56,13 @@ const HeaderButton = styled(Button)` `; const Wrapper = styled.div` - padding: ${({ theme }) => theme.gridUnit}px - ${({ theme }) => theme.gridUnit * 2}px; + ${({ theme }) => ` + padding: ${theme.gridUnit}px ${theme.gridUnit * 2}px; + + .ant-dropdown-trigger span { + padding-right: ${theme.gridUnit * 2}px; + } + `} `; type HeaderProps = { @@ -100,7 +105,7 @@ const Header: FC = ({ toggleFiltersBar }) => { {t('Filters')} - {canSetHorizontalFilterBar && } + {canSetHorizontalFilterBar && } ` + padding: ${theme.gridUnit * 2}px ${theme.gridUnit * 2}px; + background: ${theme.colors.grayscale.light5}; + box-shadow: inset 0px -2px 2px -1px ${theme.colors.grayscale.light2}; + `} +`; + +const HorizontalBarContent = styled.div` + ${({ theme }) => ` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-start; + padding: 0 ${theme.gridUnit * 2}px; + line-height: 0; + + .loading { + margin: ${theme.gridUnit * 2}px auto ${theme.gridUnit * 2}px; + padding: 0; + } + `} +`; + +const FilterBarEmptyStateContainer = styled.div` + ${({ theme }) => ` + margin: 0 ${theme.gridUnit * 2}px 0 ${theme.gridUnit * 4}px; + font-weight: ${theme.typography.weights.bold}; + color: ${theme.colors.grayscale.base}; + font-size: ${theme.typography.sizes.s}px; + `} +`; + +const FiltersLinkContainer = styled.div<{ hasFilters: boolean }>` + ${({ theme, hasFilters }) => ` + height: 24px; + display: flex; + align-items: center; + padding: 0 ${theme.gridUnit * 4}px 0 ${theme.gridUnit * 4}px; + border-right: ${ + hasFilters ? `1px solid ${theme.colors.grayscale.light2}` : 0 + }; + + button { + display: flex; + align-items: center; + > .anticon { + height: 24px; + padding-right: ${theme.gridUnit}px; + } + > .anticon + span, > .anticon { + margin-right: 0; + margin-left: 0; + } + } + `} +`; + +const HorizontalFilterBar: React.FC = ({ + actions, + canEdit, + dashboardId, + dataMaskSelected, + filterValues, + isInitialized, + directPathToChild, + onSelectionChange, +}) => { + const hasFilters = filterValues.length > 0; + + return ( + + + {!isInitialized ? ( + + ) : ( + <> + {canEdit && } + {!hasFilters && ( + + {t('No filters are currently added to this dashboard.')} + + )} + {canEdit && ( + + + {t('Add/Edit Filters')} + + + )} + {hasFilters && ( + + )} + {actions} + + )} + + + ); +}; +export default React.memo(HorizontalFilterBar); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/HorizontalFilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/HorizontalFilterBar.test.tsx new file mode 100644 index 0000000000000..4116aef6b7985 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/HorizontalFilterBar.test.tsx @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { NativeFilterType } from '@superset-ui/core'; +import React from 'react'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import HorizontalBar from './Horizontal'; + +const defaultProps = { + actions: null, + canEdit: true, + dashboardId: 1, + dataMaskSelected: {}, + filterValues: [], + isInitialized: true, + onSelectionChange: jest.fn(), +}; + +const renderWrapper = (overrideProps?: Record) => + waitFor(() => + render(, { + useRedux: true, + }), + ); + +test('should render', async () => { + const { container } = await renderWrapper(); + expect(container).toBeInTheDocument(); +}); + +test('should not render the empty message', async () => { + await renderWrapper({ + filterValues: [ + { + id: 'test', + type: NativeFilterType.NATIVE_FILTER, + }, + ], + }); + expect( + screen.queryByText('No filters are currently added to this dashboard.'), + ).not.toBeInTheDocument(); +}); + +test('should render the empty message', async () => { + await renderWrapper(); + expect( + screen.getByText('No filters are currently added to this dashboard.'), + ).toBeInTheDocument(); +}); + +test('should render the gear icon', async () => { + await renderWrapper(); + expect(screen.getByRole('img', { name: 'gear' })).toBeInTheDocument(); +}); + +test('should not render the gear icon', async () => { + await renderWrapper({ + canEdit: false, + }); + + expect(screen.queryByRole('img', { name: 'gear' })).not.toBeInTheDocument(); +}); + +test('should not render the loading icon', async () => { + await renderWrapper(); + expect( + screen.queryByRole('status', { name: 'Loading' }), + ).not.toBeInTheDocument(); +}); + +test('should render the loading icon', async () => { + await renderWrapper({ + isInitialized: false, + }); + expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); +}); + +test('should render Add/Edit Filters', async () => { + await renderWrapper(); + expect(screen.getByText('Add/Edit Filters')).toBeInTheDocument(); +}); + +test('should not render Add/Edit Filters', async () => { + await renderWrapper({ + canEdit: false, + }); + expect(screen.queryByText('Add/Edit Filters')).not.toBeInTheDocument(); +}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx new file mode 100644 index 0000000000000..54ec436ea4cbc --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx @@ -0,0 +1,316 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable no-param-reassign */ +import throttle from 'lodash/throttle'; +import React, { + useEffect, + useState, + useCallback, + useMemo, + useRef, + createContext, +} from 'react'; +import cx from 'classnames'; +import { HandlerFunction, styled, t, isNativeFilter } from '@superset-ui/core'; +import Icons from 'src/components/Icons'; +import { AntdTabs } from 'src/components'; +import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; +import Loading from 'src/components/Loading'; +import { EmptyStateSmall } from 'src/components/EmptyState'; +import { getFilterBarTestId } from './utils'; +import { TabIds, VerticalBarProps } from './types'; +import FilterSets from './FilterSets'; +import { useFilterSets } from './state'; +import EditSection from './FilterSets/EditSection'; +import Header from './Header'; +import FilterControls from './FilterControls/FilterControls'; + +const BarWrapper = styled.div<{ width: number }>` + width: ${({ theme }) => theme.gridUnit * 8}px; + + & .ant-tabs-top > .ant-tabs-nav { + margin: 0; + } + &.open { + width: ${({ width }) => width}px; // arbitrary... + } +`; + +const Bar = styled.div<{ width: number }>` + ${({ theme, width }) => ` + & .ant-typography-edit-content { + left: 0; + margin-top: 0; + width: 100%; + } + position: absolute; + top: 0; + left: 0; + flex-direction: column; + flex-grow: 1; + width: ${width}px; + background: ${theme.colors.grayscale.light5}; + border-right: 1px solid ${theme.colors.grayscale.light2}; + border-bottom: 1px solid ${theme.colors.grayscale.light2}; + min-height: 100%; + display: none; + &.open { + display: flex; + } + `} +`; + +const CollapsedBar = styled.div<{ offset: number }>` + ${({ theme, offset }) => ` + position: absolute; + top: ${offset}px; + left: 0; + height: 100%; + width: ${theme.gridUnit * 8}px; + padding-top: ${theme.gridUnit * 2}px; + display: none; + text-align: center; + &.open { + display: flex; + flex-direction: column; + align-items: center; + padding: ${theme.gridUnit * 2}px; + } + svg { + cursor: pointer; + } + `} +`; + +const StyledCollapseIcon = styled(Icons.Collapse)` + ${({ theme }) => ` + color: ${theme.colors.primary.base}; + margin-bottom: ${theme.gridUnit * 3}px; + `} +`; + +const StyledFilterIcon = styled(Icons.Filter)` + color: ${({ theme }) => theme.colors.grayscale.base}; +`; + +const StyledTabs = styled(AntdTabs)` + & .ant-tabs-nav-list { + width: 100%; + } + & .ant-tabs-tab { + display: flex; + justify-content: center; + margin: 0; + flex: 1; + } + + & > .ant-tabs-nav .ant-tabs-nav-operations { + display: none; + } +`; + +const FilterBarEmptyStateContainer = styled.div` + margin-top: ${({ theme }) => theme.gridUnit * 8}px; +`; + +const FilterControlsWrapper = styled.div` + padding: ${({ theme }) => theme.gridUnit * 4}px; + // 108px padding to make room for buttons with position: absolute + padding-bottom: ${({ theme }) => theme.gridUnit * 27}px; +`; + +export const FilterBarScrollContext = createContext(false); +const VerticalFilterBar: React.FC = ({ + actions, + canEdit, + dataMaskSelected, + directPathToChild, + filtersOpen, + filterValues, + height, + isDisabled, + isInitialized, + offset, + onSelectionChange, + toggleFiltersBar, + width, +}) => { + const [editFilterSetId, setEditFilterSetId] = useState(null); + const filterSets = useFilterSets(); + const filterSetFilterValues = Object.values(filterSets); + const [tab, setTab] = useState(TabIds.AllFilters); + const nativeFilterValues = filterValues.filter(isNativeFilter); + const [isScrolling, setIsScrolling] = useState(false); + const timeout = useRef(); + + const openFiltersBar = useCallback( + () => toggleFiltersBar(true), + [toggleFiltersBar], + ); + + const onScroll = useMemo( + () => + throttle(() => { + clearTimeout(timeout.current); + setIsScrolling(true); + timeout.current = setTimeout(() => { + setIsScrolling(false); + }, 300); + }, 200), + [], + ); + + useEffect(() => { + document.onscroll = onScroll; + return () => { + document.onscroll = null; + }; + }, [onScroll]); + + const tabPaneStyle = useMemo( + () => ({ overflow: 'auto', height, overscrollBehavior: 'contain' }), + [height], + ); + + const numberOfFilters = nativeFilterValues.length; + + return ( + + + + + + + +
+ {!isInitialized ? ( +
+ +
+ ) : isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? ( + + + {editFilterSetId && ( + setEditFilterSetId(null)} + filterSetId={editFilterSetId} + /> + )} + {filterValues.length === 0 ? ( + + + + ) : ( + + + + )} + + + + + + ) : ( +
+ {filterValues.length === 0 ? ( + + + + ) : ( + + + + )} +
+ )} + {actions} + + + + ); +}; +export default React.memo(VerticalFilterBar); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 1b13f0583a9c4..2905c6a075acf 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -18,152 +18,38 @@ */ /* eslint-disable no-param-reassign */ -import throttle from 'lodash/throttle'; -import React, { - useEffect, - useState, - useCallback, - useMemo, - useRef, - createContext, -} from 'react'; +import React, { useEffect, useState, useCallback, createContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import cx from 'classnames'; import { DataMaskStateWithId, DataMaskWithId, Filter, DataMask, - HandlerFunction, - styled, - t, SLOW_DEBOUNCE, isNativeFilter, } from '@superset-ui/core'; -import Icons from 'src/components/Icons'; -import { AntdTabs } from 'src/components'; import { useHistory } from 'react-router-dom'; import { usePrevious } from 'src/hooks/usePrevious'; -import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { updateDataMask, clearDataMask } from 'src/dataMask/actions'; import { useImmer } from 'use-immer'; import { isEmpty, isEqual, debounce } from 'lodash'; -import { testWithId } from 'src/utils/testUtils'; -import Loading from 'src/components/Loading'; import { getInitialDataMask } from 'src/dataMask/reducer'; import { URL_PARAMS } from 'src/constants'; import { getUrlParam } from 'src/utils/urlUtils'; -import { EmptyStateSmall } from 'src/components/EmptyState'; import { useTabId } from 'src/hooks/useTabId'; -import { RootState } from 'src/dashboard/types'; -import { checkIsApplyDisabled, TabIds } from './utils'; -import FilterSets from './FilterSets'; +import { FilterBarOrientation, RootState } from 'src/dashboard/types'; +import { checkIsApplyDisabled } from './utils'; +import { FiltersBarProps } from './types'; import { useNativeFiltersDataMask, useFilters, - useFilterSets, useFilterUpdates, useInitialization, } from './state'; import { createFilterKey, updateFilterKey } from './keyValue'; -import EditSection from './FilterSets/EditSection'; -import Header from './Header'; -import FilterControls from './FilterControls/FilterControls'; -import { ActionButtons } from './ActionButtons'; - -export const FILTER_BAR_TEST_ID = 'filter-bar'; -export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID); - -const BarWrapper = styled.div<{ width: number }>` - width: ${({ theme }) => theme.gridUnit * 8}px; - - & .ant-tabs-top > .ant-tabs-nav { - margin: 0; - } - &.open { - width: ${({ width }) => width}px; // arbitrary... - } -`; - -const Bar = styled.div<{ width: number }>` - & .ant-typography-edit-content { - left: 0; - margin-top: 0; - width: 100%; - } - position: absolute; - top: 0; - left: 0; - flex-direction: column; - flex-grow: 1; - width: ${({ width }) => width}px; - background: ${({ theme }) => theme.colors.grayscale.light5}; - border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - min-height: 100%; - display: none; - &.open { - display: flex; - } -`; - -const CollapsedBar = styled.div<{ offset: number }>` - position: absolute; - top: ${({ offset }) => offset}px; - left: 0; - height: 100%; - width: ${({ theme }) => theme.gridUnit * 8}px; - padding-top: ${({ theme }) => theme.gridUnit * 2}px; - display: none; - text-align: center; - &.open { - display: flex; - flex-direction: column; - align-items: center; - padding: ${({ theme }) => theme.gridUnit * 2}px; - } - svg { - cursor: pointer; - } -`; - -const StyledCollapseIcon = styled(Icons.Collapse)` - color: ${({ theme }) => theme.colors.primary.base}; - margin-bottom: ${({ theme }) => theme.gridUnit * 3}px; -`; - -const StyledFilterIcon = styled(Icons.Filter)` - color: ${({ theme }) => theme.colors.grayscale.base}; -`; - -const StyledTabs = styled(AntdTabs)` - & .ant-tabs-nav-list { - width: 100%; - } - & .ant-tabs-tab { - display: flex; - justify-content: center; - margin: 0; - flex: 1; - } - - & > .ant-tabs-nav .ant-tabs-nav-operations { - display: none; - } -`; - -const FilterBarEmptyStateContainer = styled.div` - margin-top: ${({ theme }) => theme.gridUnit * 8}px; -`; - -export interface FiltersBarProps { - filtersOpen: boolean; - toggleFiltersBar: any; - directPathToChild?: string[]; - width: number; - height: number | string; - offset: number; -} +import ActionButtons from './ActionButtons'; +import Horizontal from './Horizontal'; +import Vertical from './Vertical'; const EXCLUDED_URL_PARAMS: string[] = [ URL_PARAMS.nativeFilters.name, @@ -225,29 +111,22 @@ const publishDataMask = debounce( export const FilterBarScrollContext = createContext(false); const FilterBar: React.FC = ({ - filtersOpen, - toggleFiltersBar, directPathToChild, - width, - height, - offset, + orientation = FilterBarOrientation.VERTICAL, + verticalConfig, }) => { const history = useHistory(); const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask(); - const [editFilterSetId, setEditFilterSetId] = useState(null); const [dataMaskSelected, setDataMaskSelected] = useImmer(dataMaskApplied); const dispatch = useDispatch(); const [updateKey, setUpdateKey] = useState(0); const tabId = useTabId(); - const filterSets = useFilterSets(); - const filterSetFilterValues = Object.values(filterSets); - const [tab, setTab] = useState(TabIds.AllFilters); const filters = useFilters(); const previousFilters = usePrevious(filters); const filterValues = Object.values(filters); const nativeFilterValues = filterValues.filter(isNativeFilter); - const dashboardId = useSelector( + const dashboardId = useSelector( ({ dashboardInfo }) => dashboardInfo?.id, ); const previousDashboardId = usePrevious(dashboardId); @@ -255,9 +134,6 @@ const FilterBar: React.FC = ({ ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, ); - const [isScrolling, setIsScrolling] = useState(false); - const timeout = useRef(); - const handleFilterSelectionChange = useCallback( ( filter: Pick & Partial, @@ -352,29 +228,6 @@ const FilterBar: React.FC = ({ }); }, [dataMaskSelected, dispatch, setDataMaskSelected]); - const openFiltersBar = useCallback( - () => toggleFiltersBar(true), - [toggleFiltersBar], - ); - - const onScroll = useCallback( - throttle(() => { - clearTimeout(timeout.current); - setIsScrolling(true); - timeout.current = setTimeout(() => { - setIsScrolling(false); - }, 300); - }, 200), - [], - ); - - useEffect(() => { - document.onscroll = onScroll; - return () => { - document.onscroll = null; - }; - }, [onScroll]); - useFilterUpdates(dataMaskSelected, setDataMaskSelected); const isApplyDisabled = checkIsApplyDisabled( dataMaskSelected, @@ -382,136 +235,46 @@ const FilterBar: React.FC = ({ nativeFilterValues, ); const isInitialized = useInitialization(); - const tabPaneStyle = useMemo( - () => ({ overflow: 'auto', height, overscrollBehavior: 'contain' }), - [height], - ); - const numberOfFilters = nativeFilterValues.length; - - return ( - - - - - - - -
- {!isInitialized ? ( -
- -
- ) : isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? ( - - - {editFilterSetId && ( - setEditFilterSetId(null)} - filterSetId={editFilterSetId} - /> - )} - {filterValues.length === 0 ? ( - - - - ) : ( - - )} - - - - - - ) : ( -
- {filterValues.length === 0 ? ( - - - - ) : ( - - )} -
- )} - - - - + const actions = ( + ); + + return orientation === FilterBarOrientation.HORIZONTAL ? ( + + ) : verticalConfig ? ( + + ) : null; }; export default React.memo(FilterBar); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/types.ts new file mode 100644 index 0000000000000..ae1368eff83d6 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/types.ts @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { + DataMask, + DataMaskStateWithId, + Divider, + Filter, +} from '@superset-ui/core'; +import { FilterBarOrientation } from 'src/dashboard/types'; + +interface CommonFiltersBarProps { + actions: React.ReactNode; + canEdit: boolean; + dataMaskSelected: DataMaskStateWithId; + directPathToChild?: string[]; + filterValues: (Filter | Divider)[]; + isInitialized: boolean; + onSelectionChange: ( + filter: Pick & Partial, + dataMask: Partial, + ) => void; +} + +interface VerticalBarConfig { + filtersOpen: boolean; + height: number | string; + offset: number; + toggleFiltersBar: any; + width: number; +} + +export interface FiltersBarProps + extends Pick { + orientation: FilterBarOrientation; + verticalConfig?: VerticalBarConfig; +} + +export type HorizontalBarProps = CommonFiltersBarProps & { + dashboardId: number; +}; + +export type VerticalBarProps = Omit & + CommonFiltersBarProps & + VerticalBarConfig & { + isDisabled: boolean; + }; + +export enum TabIds { + AllFilters = 'allFilters', + FilterSets = 'filterSets', +} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterControlFactory.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterControlFactory.tsx new file mode 100644 index 0000000000000..6893e629cb49c --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterControlFactory.tsx @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useCallback, useMemo } from 'react'; +import { + DataMask, + DataMaskStateWithId, + Divider, + Filter, + FilterWithDataMask, + isFilterDivider, +} from '@superset-ui/core'; +import { FilterBarOrientation } from 'src/dashboard/types'; +import FilterControl from './FilterControls/FilterControl'; +import { useFilters } from './state'; +import FilterDivider from './FilterControls/FilterDivider'; + +export const useFilterControlFactory = ( + dataMaskSelected: DataMaskStateWithId, + directPathToChild: string[] | undefined, + onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void, +) => { + const filters = useFilters(); + const filterValues = useMemo(() => Object.values(filters), [filters]); + const filtersWithValues: (FilterWithDataMask | Divider)[] = useMemo( + () => + filterValues.map(filter => ({ + ...filter, + dataMask: dataMaskSelected[filter.id], + })), + [filterValues, dataMaskSelected], + ); + + const filterControlFactory = useCallback( + ( + index: number, + filterBarOrientation: FilterBarOrientation, + overflow: boolean, + ) => { + const filter = filtersWithValues[index]; + if (isFilterDivider(filter)) { + return ( + + ); + } + return ( + + ); + }, + [ + filtersWithValues, + dataMaskSelected, + directPathToChild, + onFilterSelectionChange, + ], + ); + + return { filterControlFactory, filtersWithValues }; +}; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts index 842bb440542c3..9aaa02fc54eac 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts @@ -19,11 +19,7 @@ import { areObjectsEqual } from 'src/reduxUtils'; import { DataMaskStateWithId, Filter, FilterState } from '@superset-ui/core'; - -export enum TabIds { - AllFilters = 'allFilters', - FilterSets = 'filterSets', -} +import { testWithId } from 'src/utils/testUtils'; export const getOnlyExtraFormData = (data: DataMaskStateWithId) => Object.values(data).reduce( @@ -65,3 +61,6 @@ export const checkIsApplyDisabled = ( ) ); }; + +export const FILTER_BAR_TEST_ID = 'filter-bar'; +export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx index bc1f7b2ea3712..8d4b9051eb717 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx @@ -27,6 +27,7 @@ export const FilterCard = ({ filter, getPopupContainer, isVisible: externalIsVisible = true, + placement, }: FilterCardProps) => { const [internalIsVisible, setInternalIsVisible] = useState(false); @@ -37,7 +38,7 @@ export const FilterCard = ({ }, [externalIsVisible]); return ( HTMLElement; isVisible?: boolean; + placement: FilterCardPlacement; } export interface FilterCardRowProps { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.ts index 030b65859b3ea..51d987a577904 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts @@ -23,6 +23,7 @@ import { FilterConfiguration, Divider, isFilterDivider, + FilterWithDataMask, } from '@superset-ui/core'; import { ActiveTabs, DashboardLayout, RootState } from '../../types'; import { TAB_TYPE } from '../../util/componentTypes'; @@ -109,13 +110,15 @@ function useIsFilterInScope() { })); } -export function useSelectFiltersInScope(filters: (Filter | Divider)[]) { +export function useSelectFiltersInScope( + filters: (FilterWithDataMask | Divider)[], +) { const dashboardHasTabs = useDashboardHasTabs(); const isFilterInScope = useIsFilterInScope(); return useMemo(() => { - let filtersInScope: (Filter | Divider)[] = []; - const filtersOutOfScope: (Filter | Divider)[] = []; + let filtersInScope: (FilterWithDataMask | Divider)[] = []; + const filtersOutOfScope: (FilterWithDataMask | Divider)[] = []; // we check native filters scopes only on dashboards with tabs if (!dashboardHasTabs) { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts index 9a21eb0e885f8..cffd0ba60611a 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts @@ -146,7 +146,7 @@ export function nativeFilterGate(behaviors: Behavior[]): boolean { const isComponentATab = ( dashboardLayout: DashboardLayout, componentId: string, -) => dashboardLayout[componentId].type === TAB_TYPE; +) => dashboardLayout[componentId]?.type === TAB_TYPE; const findTabsWithChartsInScopeHelper = ( dashboardLayout: DashboardLayout, @@ -156,19 +156,19 @@ const findTabsWithChartsInScopeHelper = ( tabsToHighlight: Set, ) => { if ( - dashboardLayout[componentId].type === CHART_TYPE && - chartsInScope.includes(dashboardLayout[componentId].meta.chartId) + dashboardLayout[componentId]?.type === CHART_TYPE && + chartsInScope.includes(dashboardLayout[componentId]?.meta?.chartId) ) { tabIds.forEach(tabsToHighlight.add, tabsToHighlight); } if ( - dashboardLayout[componentId].children.length === 0 || + dashboardLayout[componentId]?.children?.length === 0 || (isComponentATab(dashboardLayout, componentId) && tabsToHighlight.has(componentId)) ) { return; } - dashboardLayout[componentId].children.forEach(childId => + dashboardLayout[componentId]?.children.forEach(childId => findTabsWithChartsInScopeHelper( dashboardLayout, chartsInScope, @@ -188,7 +188,7 @@ export const findTabsWithChartsInScope = ( const hasTopLevelTabs = rootChildId !== DASHBOARD_GRID_ID; const tabsInScope = new Set(); if (hasTopLevelTabs) { - dashboardLayout[rootChildId].children?.forEach(tabId => + dashboardLayout[rootChildId]?.children?.forEach(tabId => findTabsWithChartsInScopeHelper( dashboardLayout, chartsInScope, @@ -199,7 +199,7 @@ export const findTabsWithChartsInScope = ( ); } else { Object.values(dashboardLayout) - .filter(element => element.type === TAB_TYPE) + .filter(element => element?.type === TAB_TYPE) .forEach(element => findTabsWithChartsInScopeHelper( dashboardLayout, diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.js b/superset-frontend/src/dashboard/reducers/dashboardInfo.js index 8a01e6122c779..030fd60250569 100644 --- a/superset-frontend/src/dashboard/reducers/dashboardInfo.js +++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.js @@ -19,7 +19,7 @@ import { DASHBOARD_INFO_UPDATED, - SET_FILTER_BAR_LOCATION, + SET_FILTER_BAR_ORIENTATION, } from '../actions/dashboardInfo'; import { HYDRATE_DASHBOARD } from '../actions/hydrate'; @@ -38,10 +38,10 @@ export default function dashboardStateReducer(state = {}, action) { ...action.data.dashboardInfo, // set async api call data }; - case SET_FILTER_BAR_LOCATION: + case SET_FILTER_BAR_ORIENTATION: return { ...state, - filterBarLocation: action.filterBarLocation, + filterBarOrientation: action.filterBarOrientation, }; default: return state; diff --git a/superset-frontend/src/dashboard/styles.ts b/superset-frontend/src/dashboard/styles.ts index 5947fd6f26145..14b9d973cfeab 100644 --- a/superset-frontend/src/dashboard/styles.ts +++ b/superset-frontend/src/dashboard/styles.ts @@ -24,9 +24,18 @@ export const filterCardPopoverStyle = (theme: SupersetTheme) => css` padding: 0; border-radius: 4px; + &.ant-popover-placement-bottom { + padding-top: ${theme.gridUnit}px; + } + + &.ant-popover-placement-left { + padding-right: ${theme.gridUnit * 3}px; + } + .ant-popover-inner { box-shadow: 0 0 8px rgb(0 0 0 / 10%); } + .ant-popover-inner-content { padding: ${theme.gridUnit * 4}px; } diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts index b809f405ac026..1bdd1c14a1c7a 100644 --- a/superset-frontend/src/dashboard/types.ts +++ b/superset-frontend/src/dashboard/types.ts @@ -52,7 +52,7 @@ export type Chart = ChartState & { }; }; -export enum FilterBarLocation { +export enum FilterBarOrientation { VERTICAL = 'VERTICAL', HORIZONTAL = 'HORIZONTAL', } @@ -108,7 +108,7 @@ export type DashboardInfo = { label_colors: JsonObject; shared_label_colors: JsonObject; }; - filterBarLocation: FilterBarLocation; + filterBarOrientation: FilterBarOrientation; }; export type ChartsState = { [key: string]: Chart }; diff --git a/superset-frontend/src/dashboard/util/getOverwriteItems.ts b/superset-frontend/src/dashboard/util/getOverwriteItems.ts index 5301cb03af4de..7492b8aeacf71 100644 --- a/superset-frontend/src/dashboard/util/getOverwriteItems.ts +++ b/superset-frontend/src/dashboard/util/getOverwriteItems.ts @@ -33,8 +33,10 @@ export default function getOverwriteItems(prev: JsonObject, next: JsonObject) { keyPath, ...(keyPath.split('.').find(key => JSON_KEYS.has(key)) ? { - oldValue: JSON.stringify(extractValue(prev, keyPath), null, 2) || '', - newValue: JSON.stringify(extractValue(next, keyPath), null, 2) || '', + oldValue: + JSON.stringify(extractValue(prev, keyPath), null, 2) || '{}', + newValue: + JSON.stringify(extractValue(next, keyPath), null, 2) || '{}', } : { oldValue: extractValue(prev, keyPath) || '', diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx index 7ed11a964bd3f..7b0ffeb3b67d0 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx @@ -37,6 +37,7 @@ import { SupersetTheme, useTheme, isDefined, + JsonValue, } from '@superset-ui/core'; import { ControlPanelSectionConfig, @@ -265,6 +266,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { const prevState = usePrevious(props.exploreState); const prevDatasource = usePrevious(props.exploreState.datasource); + const prevChartStatus = usePrevious(props.chart.chartStatus); const [showDatasourceAlert, setShowDatasourceAlert] = useState(false); @@ -276,25 +278,45 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { >(state => state.explore.controlsTransferred); useEffect(() => { - if (props.chart.chartStatus === 'success') { + let shouldUpdateControls = false; + const removeDatasourceWarningFromControl = ( + value: JsonValue | undefined, + ) => { + if ( + typeof value === 'object' && + isDefined(value) && + 'datasourceWarning' in value && + value.datasourceWarning === true + ) { + shouldUpdateControls = true; + return { ...value, datasourceWarning: false }; + } + return value; + }; + if ( + props.chart.chartStatus === 'success' && + prevChartStatus !== 'success' + ) { controlsTransferred?.forEach(controlName => { - const alteredControls = ensureIsArray( - props.controls[controlName].value, - ).map(value => { - if ( - typeof value === 'object' && - isDefined(value) && - 'datasourceWarning' in value - ) { - return { ...value, datasourceWarning: false }; - } - return value; - }); - props.actions.setControlValue(controlName, alteredControls); + shouldUpdateControls = false; + if (!isDefined(props.controls[controlName])) { + return; + } + const alteredControls = Array.isArray(props.controls[controlName].value) + ? ensureIsArray(props.controls[controlName].value)?.map( + removeDatasourceWarningFromControl, + ) + : removeDatasourceWarningFromControl( + props.controls[controlName].value, + ); + if (shouldUpdateControls) { + props.actions.setControlValue(controlName, alteredControls); + } }); } }, [ controlsTransferred, + prevChartStatus, props.actions, props.chart.chartStatus, props.controls, diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx index 64d09364f6134..42ca4bb3590b8 100644 --- a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx +++ b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx @@ -115,7 +115,7 @@ const NotFoundContent = () => ( {t('Add an annotation layer')}{' '} @@ -300,7 +300,7 @@ class AnnotationLayer extends React.PureComponent { if (isLoadingOptions) { if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { SupersetClient.get({ - endpoint: '/annotationlayermodelview/api/read?', + endpoint: '/api/v1/annotation_layer/', }).then(({ json }) => { const layers = json ? json.result.map(layer => ({ diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx index b9fc64a4c26a0..c5fed0b865f61 100644 --- a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx +++ b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx @@ -36,7 +36,7 @@ beforeAll(() => { value => value.value, ); - fetchMock.get('glob:*/annotationlayermodelview/api/read?*', { + fetchMock.get('glob:*/api/v1/annotation_layer/*', { result: [{ label: 'Chart A', value: 'a' }], }); diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx index 8e41812fb8446..231300711e272 100644 --- a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx +++ b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx @@ -94,17 +94,17 @@ test('Should open a menu', async () => { render(); expect(screen.queryByText('Edit dataset')).not.toBeInTheDocument(); - expect(screen.queryByText('Change dataset')).not.toBeInTheDocument(); + expect(screen.queryByText('Swap dataset')).not.toBeInTheDocument(); expect(screen.queryByText('View in SQL Lab')).not.toBeInTheDocument(); userEvent.click(screen.getByTestId('datasource-menu-trigger')); expect(await screen.findByText('Edit dataset')).toBeInTheDocument(); - expect(screen.getByText('Change dataset')).toBeInTheDocument(); + expect(screen.getByText('Swap dataset')).toBeInTheDocument(); expect(screen.getByText('View in SQL Lab')).toBeInTheDocument(); }); -test('Click on Change dataset option', async () => { +test('Click on Swap dataset option', async () => { const props = createProps(); SupersetClientGet.mockImplementation( async ({ endpoint }: { endpoint: string }) => { @@ -123,7 +123,7 @@ test('Click on Change dataset option', async () => { userEvent.click(screen.getByTestId('datasource-menu-trigger')); await act(async () => { - userEvent.click(screen.getByText('Change dataset')); + userEvent.click(screen.getByText('Swap dataset')); }); expect( screen.getByText( diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx index 266593adaeed1..2dfa78363f878 100644 --- a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx @@ -302,7 +302,7 @@ class DatasourceControl extends React.PureComponent { )} )} - {t('Change dataset')} + {t('Swap dataset')} {datasource && ( {t('View in SQL Lab')} )} @@ -421,7 +421,7 @@ class DatasourceControl extends React.PureComponent { this.handleMenuItemClick({ key: CHANGE_DATASET }) } > - {t('Change dataset')} + {t('Swap dataset')}

diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx index 0f7993f5c174f..c68b436a0e497 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx @@ -343,13 +343,14 @@ export default function DateFilterLabel(props: DateFilterControlProps) { {actualTimeRange} + {/* the zIndex value is from trying so that the Modal doesn't overlay the AdhocFilter when GENERIC_CHART_AXES is enabled */} {overlayContent} diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/emitFilterControl.tsx b/superset-frontend/src/explore/components/controls/XAxisSortControl.tsx similarity index 61% rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/emitFilterControl.tsx rename to superset-frontend/src/explore/components/controls/XAxisSortControl.tsx index a4c3f4a86d8af..05d27dc0d4361 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/emitFilterControl.tsx +++ b/superset-frontend/src/explore/components/controls/XAxisSortControl.tsx @@ -16,22 +16,21 @@ * specific language governing permissions and limitations * under the License. */ +import React, { useEffect, useState } from 'react'; +import SelectControl from './SelectControl'; -import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core'; +export default function XAxisSortControl(props: { + onChange: (val: string | undefined) => void; + value: string | null; + shouldReset: boolean; +}) { + const [value, setValue] = useState(props.value); + useEffect(() => { + if (props.shouldReset) { + props.onChange(undefined); + setValue(null); + } + }, [props.shouldReset, props.value]); -const enableCrossFilter = isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS); - -export const emitFilterControl = enableCrossFilter - ? [ - { - name: 'emit_filter', - config: { - type: 'CheckboxControl', - label: t('Enable dashboard cross filters'), - default: false, - renderTrigger: true, - description: t('Enable dashboard cross filters'), - }, - }, - ] - : []; + return ; +} diff --git a/superset-frontend/src/explore/components/controls/index.js b/superset-frontend/src/explore/components/controls/index.js index 23cd3a73d85b5..21e3bd4cf285d 100644 --- a/superset-frontend/src/explore/components/controls/index.js +++ b/superset-frontend/src/explore/components/controls/index.js @@ -45,6 +45,7 @@ import DndColumnSelectControl, { DndFilterSelect, DndMetricSelect, } from './DndColumnSelectControl'; +import XAxisSortControl from './XAxisSortControl'; const controlMap = { AnnotationLayerControl, @@ -74,6 +75,7 @@ const controlMap = { AdhocFilterControl, FilterBoxItemControl, ConditionalFormattingControl, + XAxisSortControl, ...sharedControlComponents, }; export default controlMap; diff --git a/superset-frontend/src/filters/components/types.ts b/superset-frontend/src/filters/components/types.ts index 2a403fe61bb73..4ab75a825c775 100644 --- a/superset-frontend/src/filters/components/types.ts +++ b/superset-frontend/src/filters/components/types.ts @@ -1,4 +1,5 @@ import { SetDataMaskHook } from '@superset-ui/core'; +import { FilterBarOrientation } from 'src/dashboard/types'; /** * Licensed to the Apache Software Foundation (ASF) under one @@ -21,6 +22,8 @@ import { SetDataMaskHook } from '@superset-ui/core'; export interface PluginFilterStylesProps { height: number; width: number; + orientation?: FilterBarOrientation; + overflow?: boolean; } export interface PluginFilterHooks { diff --git a/superset-frontend/src/hooks/useTruncation/index.ts b/superset-frontend/src/hooks/useTruncation/index.ts index 7f3e1bcadecee..5dc5550188a3d 100644 --- a/superset-frontend/src/hooks/useTruncation/index.ts +++ b/superset-frontend/src/hooks/useTruncation/index.ts @@ -16,92 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { RefObject, useLayoutEffect, useState, useRef } from 'react'; -export const useTruncation = ( - elementRef: RefObject, - plusRef?: RefObject, -) => { - const [elementsTruncated, setElementsTruncated] = useState(0); - const [hasHiddenElements, setHasHiddenElements] = useState(false); +import useTruncation from './useChildElementTruncation'; +import useCSSTextTruncation, { truncationCSS } from './useCSSTextTruncation'; - const previousEffectInfoRef = useRef({ - scrollWidth: 0, - parentElementWidth: 0, - plusRefWidth: 0, - }); - - useLayoutEffect(() => { - const currentElement = elementRef.current; - const plusRefElement = plusRef?.current; - - if (!currentElement) { - return; - } - - const { scrollWidth, clientWidth, childNodes } = currentElement; - - // By using the result of this effect to truncate content - // we're effectively changing it's size. - // That will trigger another pass at this effect. - // Depending on the content elements width, that second rerender could - // yield a different truncate count, thus potentially leading to a - // rendering loop. - // There's only a need to recompute if the parent width or the width of - // the child nodes changes. - const previousEffectInfo = previousEffectInfoRef.current; - const parentElementWidth = currentElement.parentElement?.clientWidth || 0; - const plusRefWidth = plusRefElement?.offsetWidth || 0; - previousEffectInfoRef.current = { - scrollWidth, - parentElementWidth, - plusRefWidth, - }; - - if ( - previousEffectInfo.parentElementWidth === parentElementWidth && - previousEffectInfo.scrollWidth === scrollWidth && - previousEffectInfo.plusRefWidth === plusRefWidth - ) { - return; - } - - if (scrollWidth > clientWidth) { - // "..." is around 6px wide - const truncationWidth = 6; - const plusSize = plusRefElement?.offsetWidth || 0; - const maxWidth = clientWidth - truncationWidth; - const elementsCount = childNodes.length; - - let width = 0; - let hiddenElements = 0; - for (let i = 0; i < elementsCount; i += 1) { - const itemWidth = (childNodes[i] as HTMLElement).offsetWidth; - const remainingWidth = maxWidth - truncationWidth - width - plusSize; - - // assures it shows +{number} only when the item is not visible - if (remainingWidth <= 0) { - hiddenElements += 1; - } - width += itemWidth; - } - - if (elementsCount > 1 && hiddenElements) { - setHasHiddenElements(true); - setElementsTruncated(hiddenElements); - } else { - setHasHiddenElements(false); - setElementsTruncated(1); - } - } else { - setHasHiddenElements(false); - setElementsTruncated(0); - } - }, [ - elementRef.current?.offsetWidth, - elementRef.current?.clientWidth, - elementRef, - ]); - - return [elementsTruncated, hasHiddenElements]; -}; +export { useTruncation, useCSSTextTruncation, truncationCSS }; diff --git a/superset-frontend/src/hooks/useTruncation/useCSSTextTruncation.ts b/superset-frontend/src/hooks/useTruncation/useCSSTextTruncation.ts new file mode 100644 index 0000000000000..4629331dd16ac --- /dev/null +++ b/superset-frontend/src/hooks/useTruncation/useCSSTextTruncation.ts @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { css } from '@emotion/react'; +import React, { useEffect, useRef, useState } from 'react'; + +/** + * Importable CSS that enables text truncation on fixed-width block + * elements. + */ +export const truncationCSS = css` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +/** + * This hook encapsulates logic supporting truncation of text via + * the CSS "text-overflow: ellipsis;" feature. Given the text content + * to be displayed, this hook returns a ref to attach to the text + * element and a boolean for whether that element is currently truncated. + */ +const useCSSTextTruncation = (): [ + React.RefObject, + boolean, +] => { + const [isTruncated, setIsTruncated] = useState(true); + const ref = useRef(null); + const { offsetWidth, scrollWidth } = ref.current ?? {}; + const prevWidths = useRef({ offsetWidth, scrollWidth }); + const { offsetWidth: prevOffsetWidth, scrollWidth: prevScrollWidth } = + prevWidths.current; + + useEffect(() => { + if ( + offsetWidth && + scrollWidth && + (offsetWidth !== prevOffsetWidth || scrollWidth !== prevScrollWidth) + ) { + prevWidths.current = { offsetWidth, scrollWidth }; + setIsTruncated(offsetWidth < scrollWidth); + } + }, [offsetWidth, prevOffsetWidth, prevScrollWidth, scrollWidth]); + + return [ref, isTruncated]; +}; + +export default useCSSTextTruncation; diff --git a/superset-frontend/src/hooks/useTruncation/useChildElementTruncation.ts b/superset-frontend/src/hooks/useTruncation/useChildElementTruncation.ts new file mode 100644 index 0000000000000..4f6b628642ab4 --- /dev/null +++ b/superset-frontend/src/hooks/useTruncation/useChildElementTruncation.ts @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { RefObject, useLayoutEffect, useState, useRef } from 'react'; + +/** + * This hook encapsulates logic to support truncation of child HTML + * elements contained in a fixed-width parent HTML element. Given + * a ref to the parent element and optionally a ref to the "+x" + * component that shows the number of truncated items, this hook + * will return the number of elements that are not fully visible + * (including those completely hidden) and whether any elements + * are completely hidden. + */ +const useChildElementTruncation = ( + elementRef: RefObject, + plusRef?: RefObject, +) => { + const [elementsTruncated, setElementsTruncated] = useState(0); + const [hasHiddenElements, setHasHiddenElements] = useState(false); + + const previousEffectInfoRef = useRef({ + scrollWidth: 0, + parentElementWidth: 0, + plusRefWidth: 0, + }); + + useLayoutEffect(() => { + const currentElement = elementRef.current; + const plusRefElement = plusRef?.current; + + if (!currentElement) { + return; + } + + const { scrollWidth, clientWidth, childNodes } = currentElement; + + // By using the result of this effect to truncate content + // we're effectively changing it's size. + // That will trigger another pass at this effect. + // Depending on the content elements width, that second rerender could + // yield a different truncate count, thus potentially leading to a + // rendering loop. + // There's only a need to recompute if the parent width or the width of + // the child nodes changes. + const previousEffectInfo = previousEffectInfoRef.current; + const parentElementWidth = currentElement.parentElement?.clientWidth || 0; + const plusRefWidth = plusRefElement?.offsetWidth || 0; + previousEffectInfoRef.current = { + scrollWidth, + parentElementWidth, + plusRefWidth, + }; + + if ( + previousEffectInfo.parentElementWidth === parentElementWidth && + previousEffectInfo.scrollWidth === scrollWidth && + previousEffectInfo.plusRefWidth === plusRefWidth + ) { + return; + } + + if (scrollWidth > clientWidth) { + // "..." is around 6px wide + const truncationWidth = 6; + const plusSize = plusRefElement?.offsetWidth || 0; + const maxWidth = clientWidth - truncationWidth; + const elementsCount = childNodes.length; + + let width = 0; + let hiddenElements = 0; + for (let i = 0; i < elementsCount; i += 1) { + const itemWidth = (childNodes[i] as HTMLElement).offsetWidth; + const remainingWidth = maxWidth - truncationWidth - width - plusSize; + + // assures it shows +{number} only when the item is not visible + if (remainingWidth <= 0) { + hiddenElements += 1; + } + width += itemWidth; + } + + if (elementsCount > 1 && hiddenElements) { + setHasHiddenElements(true); + setElementsTruncated(hiddenElements); + } else { + setHasHiddenElements(false); + setElementsTruncated(1); + } + } else { + setHasHiddenElements(false); + setElementsTruncated(0); + } + }, [ + elementRef.current?.offsetWidth, + elementRef.current?.clientWidth, + elementRef, + ]); + + return [elementsTruncated, hasHiddenElements]; +}; + +export default useChildElementTruncation; diff --git a/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx b/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx index 6dedb5e0ead69..3e18c3a92f248 100644 --- a/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx +++ b/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx @@ -259,9 +259,9 @@ function AnnotationList({ {t('Annotation Layer %s', annotationLayerName)} {hasHistory ? ( - Back to all + Back to all ) : ( -
Back to all + Back to all )}
diff --git a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx index 3914485a6fb74..ff583e6b9b60e 100644 --- a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx +++ b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx @@ -143,12 +143,10 @@ function AnnotationLayersList({ } if (hasHistory) { - return ( - {name} - ); + return {name}; } - return {name}; + return {name}; }, }, { @@ -324,7 +322,7 @@ function AnnotationLayersList({ }; const onLayerAdd = (id?: number) => { - window.location.href = `/annotationmodelview/${id}/annotation`; + window.location.href = `/annotationlayer/${id}/annotation`; }; const onModalHide = () => { diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx index 243ee27fe2241..8bff977d13c70 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx @@ -471,7 +471,7 @@ const ExtraOptions = ({ value={ !Object.keys(extraJson?.engine_params || {}).length ? '' - : JSON.stringify(extraJson?.engine_params) + : extraJson?.engine_params } placeholder={t('Engine Parameters')} onChange={(json: string) => diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx index d90feec116670..88efa00a64fc9 100644 --- a/superset-frontend/src/views/routes.tsx +++ b/superset-frontend/src/views/routes.tsx @@ -156,11 +156,11 @@ export const routes: Routes = [ Component: CssTemplatesList, }, { - path: '/annotationlayermodelview/list/', + path: '/annotationlayer/list/', Component: AnnotationLayersList, }, { - path: '/annotationmodelview/:annotationLayerId/annotation/', + path: '/annotationlayer/:annotationLayerId/annotation/', Component: AnnotationList, }, { diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index 9994b1dd7911d..cf1d3c831459b 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -387,6 +387,9 @@ const config = { sourceMap: true, lessOptions: { javascriptEnabled: true, + modifyVars: { + 'root-entry-name': 'default', + }, }, }, }, diff --git a/superset/annotation_layers/annotations/commands/create.py b/superset/annotation_layers/annotations/commands/create.py index dcfa6c8521b17..26cd968c5a1f4 100644 --- a/superset/annotation_layers/annotations/commands/create.py +++ b/superset/annotation_layers/annotations/commands/create.py @@ -70,7 +70,7 @@ def validate(self) -> None: # validate date time sanity if start_dttm and end_dttm and end_dttm < start_dttm: - exceptions.append(AnnotationDatesValidationError) + exceptions.append(AnnotationDatesValidationError()) if exceptions: exception = AnnotationInvalidError() diff --git a/superset/charts/commands/export.py b/superset/charts/commands/export.py index 9b3a06c473585..bb594591e2bbb 100644 --- a/superset/charts/commands/export.py +++ b/superset/charts/commands/export.py @@ -21,7 +21,6 @@ from typing import Iterator, Tuple import yaml -from werkzeug.utils import secure_filename from superset.charts.commands.exceptions import ChartNotFoundError from superset.charts.dao import ChartDAO @@ -29,6 +28,7 @@ from superset.commands.export.models import ExportModelsCommand from superset.models.slice import Slice from superset.utils.dict_import_export import EXPORT_VERSION +from superset.utils.file import get_filename logger = logging.getLogger(__name__) @@ -44,8 +44,8 @@ class ExportChartsCommand(ExportModelsCommand): @staticmethod def _export(model: Slice, export_related: bool = True) -> Iterator[Tuple[str, str]]: - chart_slug = secure_filename(model.slice_name) - file_name = f"charts/{chart_slug}_{model.id}.yaml" + file_name = get_filename(model.slice_name, model.id) + file_path = f"charts/{file_name}.yaml" payload = model.export_to_dict( recursive=False, @@ -70,7 +70,7 @@ def _export(model: Slice, export_related: bool = True) -> Iterator[Tuple[str, st payload["dataset_uuid"] = str(model.table.uuid) file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content if model.table and export_related: yield from ExportDatasetsCommand([model.table.id]).run() diff --git a/superset/charts/data/api.py b/superset/charts/data/api.py index c20fdde6fd94e..773229ad5f753 100644 --- a/superset/charts/data/api.py +++ b/superset/charts/data/api.py @@ -89,6 +89,11 @@ def get_data(self, pk: int) -> Response: description: The type in which the data should be returned schema: type: string + - in: query + name: force + description: Should the queries be forced to load from the source + schema: + type: boolean responses: 200: description: Query result @@ -130,6 +135,7 @@ def get_data(self, pk: int) -> Response: "format", ChartDataResultFormat.JSON ) json_body["result_type"] = request.args.get("type", ChartDataResultType.FULL) + json_body["force"] = request.args.get("force") try: query_context = self._create_query_context_from_form(json_body) diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 34dd44b38c7aa..dee73de17cb6b 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -71,6 +71,7 @@ # # Column schema descriptions # +id_description = "The id of the chart." slice_name_description = "The name of the chart." description_description = "A description of the chart propose." viz_type_description = "The type of chart visualization used." @@ -153,7 +154,7 @@ class ChartEntityResponseSchema(Schema): Schema for a chart object """ - slice_id = fields.Integer() + id = fields.Integer(description=id_description) slice_name = fields.String(description=slice_name_description) cache_timeout = fields.Integer(description=cache_timeout_description) changed_on = fields.String(description=changed_on_description) @@ -1204,6 +1205,7 @@ class ChartDataQueryContextSchema(Schema): force = fields.Boolean( description="Should the queries be forced to load from the source. " "Default: `false`", + allow_none=True, ) result_type = EnumField(ChartDataResultType, by_value=True) diff --git a/superset/connectors/base/models.py b/superset/connectors/base/models.py index db16ce063c66b..b1272293286d9 100644 --- a/superset/connectors/base/models.py +++ b/superset/connectors/base/models.py @@ -312,9 +312,9 @@ def data_for_slices( # pylint: disable=too-many-locals for metric in utils.get_iterable(form_data.get(metric_param) or []): metric_names.add(utils.get_metric_name(metric)) if utils.is_adhoc_metric(metric): - column_names.add( - (metric.get("column") or {}).get("column_name") - ) + column = metric.get("column") or {} + if column_name := column.get("column_name"): + column_names.add(column_name) # Columns used in query filters column_names.update( @@ -395,6 +395,7 @@ def data_for_slices( # pylint: disable=too-many-locals @staticmethod def filter_values_handler( # pylint: disable=too-many-arguments values: Optional[FilterValues], + operator: str, target_generic_type: GenericDataType, target_native_type: Optional[str] = None, is_list_target: bool = False, @@ -405,6 +406,8 @@ def filter_values_handler( # pylint: disable=too-many-arguments return None def handle_single_value(value: Optional[FilterValue]) -> Optional[FilterValue]: + if operator == utils.FilterOperator.TEMPORAL_RANGE: + return value if ( isinstance(value, (float, int)) and target_generic_type == utils.GenericDataType.TEMPORAL diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 922c78f21f2c1..a67e686ff31f0 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -80,11 +80,13 @@ from superset.common.utils.time_range_utils import get_since_until_from_time_range from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric from superset.connectors.sqla.utils import ( + find_cached_objects_in_session, get_columns_description, get_physical_table_metadata, get_virtual_table_metadata, validate_adhoc_subquery, ) +from superset.datasets.models import Dataset as NewDataset from superset.db_engine_specs.base import BaseEngineSpec, CTE_ALIAS, TimestampExpression from superset.exceptions import ( AdvancedDataTypeResponseError, @@ -804,13 +806,13 @@ def values_for_column(self, column_name: str, limit: int = 10000) -> List[Any]: if self.fetch_values_predicate: qry = qry.where(self.get_fetch_values_predicate()) - engine = self.database.get_sqla_engine() - sql = qry.compile(engine, compile_kwargs={"literal_binds": True}) - sql = self._apply_cte(sql, cte) - sql = self.mutate_query_from_config(sql) + with self.database.get_sqla_engine_with_context() as engine: + sql = qry.compile(engine, compile_kwargs={"literal_binds": True}) + sql = self._apply_cte(sql, cte) + sql = self.mutate_query_from_config(sql) - df = pd.read_sql_query(sql=sql, con=engine) - return df[column_name].to_list() + df = pd.read_sql_query(sql=sql, con=engine) + return df[column_name].to_list() def mutate_query_from_config(self, sql: str) -> str: """Apply config's SQL_QUERY_MUTATOR @@ -1413,6 +1415,7 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma target_generic_type = GenericDataType.STRING eq = self.filter_values_handler( values=val, + operator=op, target_generic_type=target_generic_type, target_native_type=col_type, is_list_target=is_list_target, @@ -2088,6 +2091,21 @@ def update_column( # pylint: disable=unused-argument # table is updated. This busts the cache key for all charts that use the table. session.execute(update(SqlaTable).where(SqlaTable.id == target.table.id)) + # TODO: This shadow writing is deprecated + # if table itself has changed, shadow-writing will happen in `after_update` anyway + if target.table not in session.dirty: + dataset: NewDataset = ( + session.query(NewDataset) + .filter_by(uuid=target.table.uuid) + .one_or_none() + ) + # Update shadow dataset and columns + # did we find the dataset? + if not dataset: + # if dataset is not found create a new copy + target.table.write_shadow_dataset() + return + @staticmethod def after_insert( mapper: Mapper, @@ -2099,6 +2117,9 @@ def after_insert( """ security_manager.dataset_after_insert(mapper, connection, sqla_table) + # TODO: deprecated + sqla_table.write_shadow_dataset() + @staticmethod def after_delete( mapper: Mapper, @@ -2117,11 +2138,53 @@ def after_update( sqla_table: "SqlaTable", ) -> None: """ - Update dataset permissions after update + Update dataset permissions """ # set permissions security_manager.dataset_after_update(mapper, connection, sqla_table) + # TODO: the shadow writing is deprecated + inspector = inspect(sqla_table) + session = inspector.session + + # double-check that ``UPDATE``s are actually pending (this method is called even + # for instances that have no net changes to their column-based attributes) + if not session.is_modified(sqla_table, include_collections=True): + return + + # find the dataset from the known instance list first + # (it could be either from a previous query or newly created) + dataset = next( + find_cached_objects_in_session( + session, NewDataset, uuids=[sqla_table.uuid] + ), + None, + ) + # if not found, pull from database + if not dataset: + dataset = ( + session.query(NewDataset).filter_by(uuid=sqla_table.uuid).one_or_none() + ) + if not dataset: + sqla_table.write_shadow_dataset() + return + + def write_shadow_dataset( + self: "SqlaTable", + ) -> None: + """ + This method is deprecated + """ + session = inspect(self).session + # most of the write_shadow_dataset functionality has been removed + # but leaving this portion in + # to remove later because it is adding a Database relationship to the session + # and there is some functionality that depends on this + if self.database_id and ( + not self.database or self.database.id != self.database_id + ): + self.database = session.query(Database).filter_by(id=self.database_id).one() + sa.event.listen(SqlaTable, "before_update", SqlaTable.before_update) sa.event.listen(SqlaTable, "after_update", SqlaTable.after_update) diff --git a/superset/connectors/sqla/utils.py b/superset/connectors/sqla/utils.py index 8151bfd44b03b..05cf8cea13249 100644 --- a/superset/connectors/sqla/utils.py +++ b/superset/connectors/sqla/utils.py @@ -112,7 +112,6 @@ def get_virtual_table_metadata(dataset: SqlaTable) -> List[ResultSetColumnType]: ) db_engine_spec = dataset.database.db_engine_spec - engine = dataset.database.get_sqla_engine(schema=dataset.schema) sql = dataset.get_template_processor().process_template( dataset.sql, **dataset.template_params_dict ) @@ -137,13 +136,18 @@ def get_virtual_table_metadata(dataset: SqlaTable) -> List[ResultSetColumnType]: # TODO(villebro): refactor to use same code that's used by # sql_lab.py:execute_sql_statements try: - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - query = dataset.database.apply_limit_to_sql(statements[0], limit=1) - db_engine_spec.execute(cursor, query) - result = db_engine_spec.fetch_data(cursor, limit=1) - result_set = SupersetResultSet(result, cursor.description, db_engine_spec) - cols = result_set.columns + with dataset.database.get_sqla_engine_with_context( + schema=dataset.schema + ) as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + query = dataset.database.apply_limit_to_sql(statements[0], limit=1) + db_engine_spec.execute(cursor, query) + result = db_engine_spec.fetch_data(cursor, limit=1) + result_set = SupersetResultSet( + result, cursor.description, db_engine_spec + ) + cols = result_set.columns except Exception as ex: raise SupersetGenericDBErrorException(message=str(ex)) from ex return cols @@ -155,14 +159,17 @@ def get_columns_description( ) -> List[ResultSetColumnType]: db_engine_spec = database.db_engine_spec try: - with closing(database.get_sqla_engine().raw_connection()) as conn: - cursor = conn.cursor() - query = database.apply_limit_to_sql(query, limit=1) - cursor.execute(query) - db_engine_spec.execute(cursor, query) - result = db_engine_spec.fetch_data(cursor, limit=1) - result_set = SupersetResultSet(result, cursor.description, db_engine_spec) - return result_set.columns + with database.get_sqla_engine_with_context() as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + query = database.apply_limit_to_sql(query, limit=1) + cursor.execute(query) + db_engine_spec.execute(cursor, query) + result = db_engine_spec.fetch_data(cursor, limit=1) + result_set = SupersetResultSet( + result, cursor.description, db_engine_spec + ) + return result_set.columns except Exception as ex: raise SupersetGenericDBErrorException(message=str(ex)) from ex diff --git a/superset/dashboards/commands/export.py b/superset/dashboards/commands/export.py index 2d131d8f84e1c..c175556943874 100644 --- a/superset/dashboards/commands/export.py +++ b/superset/dashboards/commands/export.py @@ -23,7 +23,6 @@ from typing import Any, Dict, Iterator, Optional, Set, Tuple import yaml -from werkzeug.utils import secure_filename from superset.charts.commands.export import ExportChartsCommand from superset.dashboards.commands.exceptions import DashboardNotFoundError @@ -35,6 +34,7 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.utils.dict_import_export import EXPORT_VERSION +from superset.utils.file import get_filename logger = logging.getLogger(__name__) @@ -111,8 +111,8 @@ class ExportDashboardsCommand(ExportModelsCommand): def _export( model: Dashboard, export_related: bool = True ) -> Iterator[Tuple[str, str]]: - dashboard_slug = secure_filename(model.dashboard_title) - file_name = f"dashboards/{dashboard_slug}_{model.id}.yaml" + file_name = get_filename(model.dashboard_title, model.id) + file_path = f"dashboards/{file_name}.yaml" payload = model.export_to_dict( recursive=False, @@ -163,7 +163,7 @@ def _export( payload["version"] = EXPORT_VERSION file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content if export_related: chart_ids = [chart.id for chart in model.slices] diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index e6db0b5688b6e..d3a9444980966 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -133,7 +133,7 @@ class DashboardJSONMetadataSchema(Schema): # used for v0 import/export import_time = fields.Integer() remote_id = fields.Integer() - filter_bar_location = fields.Str(allow_none=True) + filter_bar_orientation = fields.Str(allow_none=True) class UserSchema(Schema): diff --git a/superset/databases/commands/export.py b/superset/databases/commands/export.py index 9e8cb7e374426..4d3bb7f99f251 100644 --- a/superset/databases/commands/export.py +++ b/superset/databases/commands/export.py @@ -21,13 +21,13 @@ from typing import Any, Dict, Iterator, Tuple import yaml -from werkzeug.utils import secure_filename from superset.databases.commands.exceptions import DatabaseNotFoundError from superset.databases.dao import DatabaseDAO from superset.commands.export.models import ExportModelsCommand from superset.models.core import Database from superset.utils.dict_import_export import EXPORT_VERSION +from superset.utils.file import get_filename logger = logging.getLogger(__name__) @@ -58,8 +58,8 @@ class ExportDatabasesCommand(ExportModelsCommand): def _export( model: Database, export_related: bool = True ) -> Iterator[Tuple[str, str]]: - database_slug = secure_filename(model.database_name) - file_name = f"databases/{database_slug}.yaml" + db_file_name = get_filename(model.database_name, model.id, skip_id=True) + file_path = f"databases/{db_file_name}.yaml" payload = model.export_to_dict( recursive=False, @@ -90,12 +90,14 @@ def _export( payload["version"] = EXPORT_VERSION file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content if export_related: for dataset in model.tables: - dataset_slug = secure_filename(dataset.table_name) - file_name = f"datasets/{database_slug}/{dataset_slug}.yaml" + ds_file_name = get_filename( + dataset.table_name, dataset.id, skip_id=True + ) + file_path = f"datasets/{db_file_name}/{ds_file_name}.yaml" payload = dataset.export_to_dict( recursive=True, @@ -107,4 +109,4 @@ def _export( payload["database_uuid"] = str(model.uuid) file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content diff --git a/superset/databases/commands/test_connection.py b/superset/databases/commands/test_connection.py index 167b5657fbfdc..3393be67b7f83 100644 --- a/superset/databases/commands/test_connection.py +++ b/superset/databases/commands/test_connection.py @@ -90,7 +90,6 @@ def run(self) -> None: # pylint: disable=too-many-statements database.set_sqlalchemy_uri(uri) database.db_engine_spec.mutate_db_for_connection_test(database) - engine = database.get_sqla_engine() event_logger.log_with_context( action="test_connection_attempt", engine=database.db_engine_spec.__name__, @@ -100,31 +99,32 @@ def ping(engine: Engine) -> bool: with closing(engine.raw_connection()) as conn: return engine.dialect.do_ping(conn) - try: - alive = func_timeout( - int(app.config["TEST_DATABASE_CONNECTION_TIMEOUT"].total_seconds()), - ping, - args=(engine,), - ) - except (sqlite3.ProgrammingError, RuntimeError): - # SQLite can't run on a separate thread, so ``func_timeout`` fails - # RuntimeError catches the equivalent error from duckdb. - alive = engine.dialect.do_ping(engine) - except FunctionTimedOut as ex: - raise SupersetTimeoutException( - error_type=SupersetErrorType.CONNECTION_DATABASE_TIMEOUT, - message=( - "Please check your connection details and database settings, " - "and ensure that your database is accepting connections, " - "then try connecting again." - ), - level=ErrorLevel.ERROR, - extra={"sqlalchemy_uri": database.sqlalchemy_uri}, - ) from ex - except Exception as ex: # pylint: disable=broad-except - alive = False - # So we stop losing the original message if any - ex_str = str(ex) + with database.get_sqla_engine_with_context() as engine: + try: + alive = func_timeout( + app.config["TEST_DATABASE_CONNECTION_TIMEOUT"].total_seconds(), + ping, + args=(engine,), + ) + except (sqlite3.ProgrammingError, RuntimeError): + # SQLite can't run on a separate thread, so ``func_timeout`` fails + # RuntimeError catches the equivalent error from duckdb. + alive = engine.dialect.do_ping(engine) + except FunctionTimedOut as ex: + raise SupersetTimeoutException( + error_type=SupersetErrorType.CONNECTION_DATABASE_TIMEOUT, + message=( + "Please check your connection details and database settings, " + "and ensure that your database is accepting connections, " + "then try connecting again." + ), + level=ErrorLevel.ERROR, + extra={"sqlalchemy_uri": database.sqlalchemy_uri}, + ) from ex + except Exception as ex: # pylint: disable=broad-except + alive = False + # So we stop losing the original message if any + ex_str = str(ex) if not alive: raise DBAPIError(ex_str or None, None, None) diff --git a/superset/databases/commands/validate.py b/superset/databases/commands/validate.py index a8956257fa28a..8c58ef5de0bfb 100644 --- a/superset/databases/commands/validate.py +++ b/superset/databases/commands/validate.py @@ -101,21 +101,22 @@ def run(self) -> None: database.set_sqlalchemy_uri(sqlalchemy_uri) database.db_engine_spec.mutate_db_for_connection_test(database) - engine = database.get_sqla_engine() - try: - with closing(engine.raw_connection()) as conn: - alive = engine.dialect.do_ping(conn) - except Exception as ex: - url = make_url_safe(sqlalchemy_uri) - context = { - "hostname": url.host, - "password": url.password, - "port": url.port, - "username": url.username, - "database": url.database, - } - errors = database.db_engine_spec.extract_errors(ex, context) - raise DatabaseTestConnectionFailedError(errors) from ex + alive = False + with database.get_sqla_engine_with_context() as engine: + try: + with closing(engine.raw_connection()) as conn: + alive = engine.dialect.do_ping(conn) + except Exception as ex: + url = make_url_safe(sqlalchemy_uri) + context = { + "hostname": url.host, + "password": url.password, + "port": url.port, + "username": url.username, + "database": url.database, + } + errors = database.db_engine_spec.extract_errors(ex, context) + raise DatabaseTestConnectionFailedError(errors) from ex if not alive: raise DatabaseOfflineError( diff --git a/superset/datasets/commands/export.py b/superset/datasets/commands/export.py index be9210a06c669..b71a95936ab1d 100644 --- a/superset/datasets/commands/export.py +++ b/superset/datasets/commands/export.py @@ -21,13 +21,13 @@ from typing import Iterator, Tuple import yaml -from werkzeug.utils import secure_filename from superset.commands.export.models import ExportModelsCommand from superset.connectors.sqla.models import SqlaTable from superset.datasets.commands.exceptions import DatasetNotFoundError from superset.datasets.dao import DatasetDAO from superset.utils.dict_import_export import EXPORT_VERSION +from superset.utils.file import get_filename logger = logging.getLogger(__name__) @@ -43,9 +43,11 @@ class ExportDatasetsCommand(ExportModelsCommand): def _export( model: SqlaTable, export_related: bool = True ) -> Iterator[Tuple[str, str]]: - database_slug = secure_filename(model.database.database_name) - dataset_slug = secure_filename(model.table_name) - file_name = f"datasets/{database_slug}/{dataset_slug}.yaml" + db_file_name = get_filename( + model.database.database_name, model.database.id, skip_id=True + ) + ds_file_name = get_filename(model.table_name, model.id, skip_id=True) + file_path = f"datasets/{db_file_name}/{ds_file_name}.yaml" payload = model.export_to_dict( recursive=True, @@ -75,11 +77,11 @@ def _export( payload["database_uuid"] = str(model.database.uuid) file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content # include database as well if export_related: - file_name = f"databases/{database_slug}.yaml" + file_path = f"databases/{db_file_name}.yaml" payload = model.database.export_to_dict( recursive=False, @@ -98,4 +100,4 @@ def _export( payload["version"] = EXPORT_VERSION file_content = yaml.safe_dump(payload, sort_keys=False) - yield file_name, file_content + yield file_path, file_content diff --git a/superset/datasets/commands/importers/v1/utils.py b/superset/datasets/commands/importers/v1/utils.py index ba2b7df26174a..d04763c7a8996 100644 --- a/superset/datasets/commands/importers/v1/utils.py +++ b/superset/datasets/commands/importers/v1/utils.py @@ -166,17 +166,26 @@ def load_data( if database.sqlalchemy_uri == current_app.config.get("SQLALCHEMY_DATABASE_URI"): logger.info("Loading data inside the import transaction") connection = session.connection() + df.to_sql( + dataset.table_name, + con=connection, + schema=dataset.schema, + if_exists="replace", + chunksize=CHUNKSIZE, + dtype=dtype, + index=False, + method="multi", + ) else: logger.warning("Loading data outside the import transaction") - connection = database.get_sqla_engine() - - df.to_sql( - dataset.table_name, - con=connection, - schema=dataset.schema, - if_exists="replace", - chunksize=CHUNKSIZE, - dtype=dtype, - index=False, - method="multi", - ) + with database.get_sqla_engine_with_context() as engine: + df.to_sql( + dataset.table_name, + con=engine, + schema=dataset.schema, + if_exists="replace", + chunksize=CHUNKSIZE, + dtype=dtype, + index=False, + method="multi", + ) diff --git a/superset/datasets/dao.py b/superset/datasets/dao.py index aed50574cb989..d260df3610002 100644 --- a/superset/datasets/dao.py +++ b/superset/datasets/dao.py @@ -15,11 +15,9 @@ # specific language governing permissions and limitations # under the License. import logging -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional -from flask_appbuilder.models.sqla.interface import SQLAInterface from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import joinedload from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn from superset.dao.base import BaseDAO @@ -37,26 +35,6 @@ class DatasetDAO(BaseDAO): # pylint: disable=too-many-public-methods model_cls = SqlaTable base_filter = DatasourceFilter - @classmethod - def find_by_ids(cls, model_ids: Union[List[str], List[int]]) -> List[SqlaTable]: - """ - Find a List of models by a list of ids, if defined applies `base_filter` - """ - id_col = getattr(SqlaTable, cls.id_column_name, None) - if id_col is None: - return [] - - # the joinedload option ensures that the database is - # available in the session later and not lazy loaded - query = ( - db.session.query(SqlaTable) - .options(joinedload(SqlaTable.database)) - .filter(id_col.in_(model_ids)) - ) - data_model = SQLAInterface(SqlaTable, db.session) - query = DatasourceFilter(cls.id_column_name, data_model).apply(query, None) - return query.all() - @staticmethod def get_database_by_id(database_id: int) -> Optional[Database]: try: diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py index fb1430ba5efab..87951d396ef10 100644 --- a/superset/db_engine_specs/base.py +++ b/superset/db_engine_specs/base.py @@ -23,6 +23,7 @@ from typing import ( Any, Callable, + ContextManager, Dict, List, Match, @@ -480,8 +481,16 @@ def get_engine( database: "Database", schema: Optional[str] = None, source: Optional[utils.QuerySource] = None, - ) -> Engine: - return database.get_sqla_engine(schema=schema, source=source) + ) -> ContextManager[Engine]: + """ + Return an engine context manager. + + >>> with DBEngineSpec.get_engine(database, schema, source) as engine: + ... connection = engine.connect() + ... connection.execute(sql) + + """ + return database.get_sqla_engine_with_context(schema=schema, source=source) @classmethod def get_timestamp_expr( @@ -903,17 +912,17 @@ def df_to_sql( :param to_sql_kwargs: The kwargs to be passed to pandas.DataFrame.to_sql` method """ - engine = cls.get_engine(database) to_sql_kwargs["name"] = table.table if table.schema: # Only add schema when it is preset and non empty. to_sql_kwargs["schema"] = table.schema - if engine.dialect.supports_multivalues_insert: - to_sql_kwargs["method"] = "multi" + with cls.get_engine(database) as engine: + if engine.dialect.supports_multivalues_insert: + to_sql_kwargs["method"] = "multi" - df.to_sql(con=engine, **to_sql_kwargs) + df.to_sql(con=engine, **to_sql_kwargs) @classmethod def convert_dttm( # pylint: disable=unused-argument @@ -1025,7 +1034,7 @@ def get_table_names( # pylint: disable=unused-argument database: "Database", inspector: Inspector, schema: Optional[str], - ) -> List[str]: + ) -> Set[str]: """ Get all the real table names within the specified schema. @@ -1039,13 +1048,13 @@ def get_table_names( # pylint: disable=unused-argument """ try: - tables = inspector.get_table_names(schema) + tables = set(inspector.get_table_names(schema)) except Exception as ex: raise cls.get_dbapi_mapped_exception(ex) from ex if schema and cls.try_remove_schema_from_table_name: - tables = [re.sub(f"^{schema}\\.", "", table) for table in tables] - return sorted(tables) + tables = {re.sub(f"^{schema}\\.", "", table) for table in tables} + return tables @classmethod def get_view_names( # pylint: disable=unused-argument @@ -1053,7 +1062,7 @@ def get_view_names( # pylint: disable=unused-argument database: "Database", inspector: Inspector, schema: Optional[str], - ) -> List[str]: + ) -> Set[str]: """ Get all the view names within the specified schema. @@ -1067,13 +1076,13 @@ def get_view_names( # pylint: disable=unused-argument """ try: - views = inspector.get_view_names(schema) + views = set(inspector.get_view_names(schema)) except Exception as ex: raise cls.get_dbapi_mapped_exception(ex) from ex if schema and cls.try_remove_schema_from_table_name: - views = [re.sub(f"^{schema}\\.", "", view) for view in views] - return sorted(views) + views = {re.sub(f"^{schema}\\.", "", view) for view in views} + return views @classmethod def get_table_comment( @@ -1286,13 +1295,15 @@ def estimate_query_cost( parsed_query = sql_parse.ParsedQuery(sql) statements = parsed_query.get_statements() - engine = cls.get_engine(database, schema=schema, source=source) costs = [] - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - for statement in statements: - processed_statement = cls.process_statement(statement, database) - costs.append(cls.estimate_statement_cost(processed_statement, cursor)) + with cls.get_engine(database, schema=schema, source=source) as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + for statement in statements: + processed_statement = cls.process_statement(statement, database) + costs.append( + cls.estimate_statement_cost(processed_statement, cursor) + ) return costs @classmethod diff --git a/superset/db_engine_specs/bigquery.py b/superset/db_engine_specs/bigquery.py index 373fc2f747375..bcaec8f6ec6c1 100644 --- a/superset/db_engine_specs/bigquery.py +++ b/superset/db_engine_specs/bigquery.py @@ -340,8 +340,12 @@ def df_to_sql( if not table.schema: raise Exception("The table schema must be defined") - engine = cls.get_engine(database) - to_gbq_kwargs = {"destination_table": str(table), "project_id": engine.url.host} + to_gbq_kwargs = {} + with cls.get_engine(database) as engine: + to_gbq_kwargs = { + "destination_table": str(table), + "project_id": engine.url.host, + } # Add credentials if they are set on the SQLAlchemy dialect. creds = engine.dialect.credentials_info diff --git a/superset/db_engine_specs/databricks.py b/superset/db_engine_specs/databricks.py index 90d90b9448fa7..8dce8a5940613 100644 --- a/superset/db_engine_specs/databricks.py +++ b/superset/db_engine_specs/databricks.py @@ -16,7 +16,7 @@ # under the License. from datetime import datetime -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import Any, Dict, Optional, Set, TYPE_CHECKING from sqlalchemy.engine.reflection import Inspector @@ -103,9 +103,7 @@ def get_table_names( database: "Database", inspector: Inspector, schema: Optional[str], - ) -> List[str]: - tables = set(super().get_table_names(database, inspector, schema)) - views = set(cls.get_view_names(database, inspector, schema)) - actual_tables = tables - views - - return list(actual_tables) + ) -> Set[str]: + return super().get_table_names( + database, inspector, schema + ) - cls.get_view_names(database, inspector, schema) diff --git a/superset/db_engine_specs/duckdb.py b/superset/db_engine_specs/duckdb.py index 577098a1ca572..c9eb287c9e44e 100644 --- a/superset/db_engine_specs/duckdb.py +++ b/superset/db_engine_specs/duckdb.py @@ -18,7 +18,7 @@ import re from datetime import datetime -from typing import Any, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING +from typing import Any, Dict, Optional, Pattern, Set, Tuple, TYPE_CHECKING from flask_babel import gettext as __ from sqlalchemy.engine.reflection import Inspector @@ -75,5 +75,5 @@ def convert_dttm( @classmethod def get_table_names( cls, database: Database, inspector: Inspector, schema: Optional[str] - ) -> List[str]: - return sorted(inspector.get_table_names(schema)) + ) -> Set[str]: + return set(inspector.get_table_names(schema)) diff --git a/superset/db_engine_specs/dynamodb.py b/superset/db_engine_specs/dynamodb.py new file mode 100644 index 0000000000000..06dcafbb5de3b --- /dev/null +++ b/superset/db_engine_specs/dynamodb.py @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from datetime import datetime +from typing import Any, Dict, Optional + +from superset.db_engine_specs.base import BaseEngineSpec +from superset.utils import core as utils + + +class DynamoDBEngineSpec(BaseEngineSpec): + engine = "dynamodb" + engine_name = "Amazon DynamoDB" + + _time_grain_expressions = { + None: "{col}", + "PT1S": "DATETIME(STRFTIME('%Y-%m-%dT%H:%M:%S', {col}))", + "PT1M": "DATETIME(STRFTIME('%Y-%m-%dT%H:%M:00', {col}))", + "PT1H": "DATETIME(STRFTIME('%Y-%m-%dT%H:00:00', {col}))", + "P1D": "DATETIME({col}, 'start of day')", + "P1W": "DATETIME({col}, 'start of day', -strftime('%w', {col}) || ' days')", + "P1M": "DATETIME({col}, 'start of month')", + "P3M": ( + "DATETIME({col}, 'start of month', " + "printf('-%d month', (strftime('%m', {col}) - 1) % 3))" + ), + "P1Y": "DATETIME({col}, 'start of year')", + "P1W/1970-01-03T00:00:00Z": "DATETIME({col}, 'start of day', 'weekday 6')", + "P1W/1970-01-04T00:00:00Z": "DATETIME({col}, 'start of day', 'weekday 0')", + "1969-12-28T00:00:00Z/P1W": ( + "DATETIME({col}, 'start of day', 'weekday 0', '-7 days')" + ), + "1969-12-29T00:00:00Z/P1W": ( + "DATETIME({col}, 'start of day', 'weekday 1', '-7 days')" + ), + } + + @classmethod + def epoch_to_dttm(cls) -> str: + return "datetime({col}, 'unixepoch')" + + @classmethod + def convert_dttm( + cls, target_type: str, dttm: datetime, db_extra: Optional[Dict[str, Any]] = None + ) -> Optional[str]: + tt = target_type.upper() + if tt in (utils.TemporalType.TEXT, utils.TemporalType.DATETIME): + return f"""'{dttm.isoformat(sep=" ", timespec="seconds")}'""" + return None diff --git a/superset/db_engine_specs/gsheets.py b/superset/db_engine_specs/gsheets.py index fd1a2754d76bc..805a7ee400cfd 100644 --- a/superset/db_engine_specs/gsheets.py +++ b/superset/db_engine_specs/gsheets.py @@ -109,11 +109,11 @@ def extra_table_metadata( table_name: str, schema_name: Optional[str], ) -> Dict[str, Any]: - engine = cls.get_engine(database, schema=schema_name) - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - cursor.execute(f'SELECT GET_METADATA("{table_name}")') - results = cursor.fetchone()[0] + with cls.get_engine(database, schema=schema_name) as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + cursor.execute(f'SELECT GET_METADATA("{table_name}")') + results = cursor.fetchone()[0] try: metadata = json.loads(results) diff --git a/superset/db_engine_specs/hive.py b/superset/db_engine_specs/hive.py index b37348e911ece..3c541c357ea59 100644 --- a/superset/db_engine_specs/hive.py +++ b/superset/db_engine_specs/hive.py @@ -185,8 +185,6 @@ def df_to_sql( :param to_sql_kwargs: The kwargs to be passed to pandas.DataFrame.to_sql` method """ - engine = cls.get_engine(database) - if to_sql_kwargs["if_exists"] == "append": raise SupersetException("Append operation not currently supported") @@ -205,7 +203,8 @@ def df_to_sql( if table_exists: raise SupersetException("Table already exists") elif to_sql_kwargs["if_exists"] == "replace": - engine.execute(f"DROP TABLE IF EXISTS {str(table)}") + with cls.get_engine(database) as engine: + engine.execute(f"DROP TABLE IF EXISTS {str(table)}") def _get_hive_type(dtype: np.dtype) -> str: hive_type_by_dtype = { diff --git a/superset/db_engine_specs/postgres.py b/superset/db_engine_specs/postgres.py index f9d450a3e9c9e..286b6e80a1ca7 100644 --- a/superset/db_engine_specs/postgres.py +++ b/superset/db_engine_specs/postgres.py @@ -18,7 +18,7 @@ import logging import re from datetime import datetime -from typing import Any, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Pattern, Set, Tuple, TYPE_CHECKING from flask_babel import gettext as __ from sqlalchemy.dialects.postgresql import ARRAY, DOUBLE_PRECISION, ENUM, JSON @@ -228,11 +228,11 @@ def query_cost_formatter( @classmethod def get_table_names( cls, database: "Database", inspector: PGInspector, schema: Optional[str] - ) -> List[str]: + ) -> Set[str]: """Need to consider foreign tables for PostgreSQL""" - tables = inspector.get_table_names(schema) - tables.extend(inspector.get_foreign_table_names(schema)) - return sorted(tables) + return set(inspector.get_table_names(schema)) | set( + inspector.get_foreign_table_names(schema) + ) @classmethod def convert_dttm( diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py index e959eb219506a..675503973485a 100644 --- a/superset/db_engine_specs/presto.py +++ b/superset/db_engine_specs/presto.py @@ -26,7 +26,18 @@ from datetime import datetime from distutils.version import StrictVersion from textwrap import dedent -from typing import Any, cast, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING, Union +from typing import ( + Any, + cast, + Dict, + List, + Optional, + Pattern, + Set, + Tuple, + TYPE_CHECKING, + Union, +) from urllib import parse import pandas as pd @@ -396,7 +407,7 @@ def get_table_names( database: Database, inspector: Inspector, schema: Optional[str], - ) -> List[str]: + ) -> Set[str]: """ Get all the real table names within the specified schema. @@ -414,12 +425,9 @@ def get_table_names( :returns: The physical table names """ - return sorted( - list( - set(super().get_table_names(database, inspector, schema)) - - set(cls.get_view_names(database, inspector, schema)) - ) - ) + return super().get_table_names( + database, inspector, schema + ) - cls.get_view_names(database, inspector, schema) @classmethod def get_view_names( @@ -427,7 +435,7 @@ def get_view_names( database: Database, inspector: Inspector, schema: Optional[str], - ) -> List[str]: + ) -> Set[str]: """ Get all the view names within the specified schema. @@ -462,14 +470,13 @@ def get_view_names( ).strip() params = {} - engine = cls.get_engine(database, schema=schema) + with cls.get_engine(database, schema=schema) as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + cursor.execute(sql, params) + results = cursor.fetchall() - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - cursor.execute(sql, params) - results = cursor.fetchall() - - return sorted([row[0] for row in results]) + return {row[0] for row in results} @classmethod def _create_column_info( @@ -989,17 +996,17 @@ def get_create_view( # pylint: disable=import-outside-toplevel from pyhive.exc import DatabaseError - engine = cls.get_engine(database, schema) - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - sql = f"SHOW CREATE VIEW {schema}.{table}" - try: - cls.execute(cursor, sql) - - except DatabaseError: # not a VIEW - return None - rows = cls.fetch_data(cursor, 1) - return rows[0][0] + with cls.get_engine(database, schema=schema) as engine: + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + sql = f"SHOW CREATE VIEW {schema}.{table}" + try: + cls.execute(cursor, sql) + + except DatabaseError: # not a VIEW + return None + rows = cls.fetch_data(cursor, 1) + return rows[0][0] @classmethod def get_tracking_url(cls, cursor: "Cursor") -> Optional[str]: diff --git a/superset/db_engine_specs/sqlite.py b/superset/db_engine_specs/sqlite.py index 85442aa877363..8bd2d081ee9e3 100644 --- a/superset/db_engine_specs/sqlite.py +++ b/superset/db_engine_specs/sqlite.py @@ -16,7 +16,7 @@ # under the License. import re from datetime import datetime -from typing import Any, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING +from typing import Any, Dict, Optional, Pattern, Set, Tuple, TYPE_CHECKING from flask_babel import gettext as __ from sqlalchemy.engine.reflection import Inspector @@ -88,6 +88,6 @@ def convert_dttm( @classmethod def get_table_names( cls, database: "Database", inspector: Inspector, schema: Optional[str] - ) -> List[str]: + ) -> Set[str]: """Need to disregard the schema for Sqlite""" - return sorted(inspector.get_table_names()) + return set(inspector.get_table_names()) diff --git a/superset/examples/bart_lines.py b/superset/examples/bart_lines.py index 91257058be75a..5d167b02d0627 100644 --- a/superset/examples/bart_lines.py +++ b/superset/examples/bart_lines.py @@ -29,31 +29,31 @@ def load_bart_lines(only_metadata: bool = False, force: bool = False) -> None: tbl_name = "bart_lines" database = get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("bart-lines.json.gz") - df = pd.read_json(url, encoding="latin-1", compression="gzip") - df["path_json"] = df.path.map(json.dumps) - df["polyline"] = df.path.map(polyline.encode) - del df["path"] + if not only_metadata and (not table_exists or force): + url = get_example_url("bart-lines.json.gz") + df = pd.read_json(url, encoding="latin-1", compression="gzip") + df["path_json"] = df.path.map(json.dumps) + df["polyline"] = df.path.map(polyline.encode) + del df["path"] - df.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "color": String(255), - "name": String(255), - "polyline": Text, - "path_json": Text, - }, - index=False, - ) + df.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "color": String(255), + "name": String(255), + "polyline": Text, + "path_json": Text, + }, + index=False, + ) print("Creating table {} reference".format(tbl_name)) table = get_table_connector_registry() diff --git a/superset/examples/birth_names.py b/superset/examples/birth_names.py index f8b8a8ecf7ca8..406a70b2cc4d5 100644 --- a/superset/examples/birth_names.py +++ b/superset/examples/birth_names.py @@ -76,25 +76,25 @@ def load_data(tbl_name: str, database: Database, sample: bool = False) -> None: pdf.ds = pd.to_datetime(pdf.ds, unit="ms") pdf = pdf.head(100) if sample else pdf - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - - pdf.to_sql( - tbl_name, - database.get_sqla_engine(), - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - # TODO(bkyryliuk): use TIMESTAMP type for presto - "ds": DateTime if database.backend != "presto" else String(255), - "gender": String(16), - "state": String(10), - "name": String(255), - }, - method="multi", - index=False, - ) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + # TODO(bkyryliuk): use TIMESTAMP type for presto + "ds": DateTime if database.backend != "presto" else String(255), + "gender": String(16), + "state": String(10), + "name": String(255), + }, + method="multi", + index=False, + ) print("Done loading table!") print("-" * 80) @@ -104,8 +104,8 @@ def load_birth_names( ) -> None: """Loading birth name dataset from a zip file in the repo""" database = get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name tbl_name = "birth_names" table_exists = database.has_table_by_name(tbl_name, schema=schema) diff --git a/superset/examples/country_map.py b/superset/examples/country_map.py index 302b55180ea84..4331033ca8369 100644 --- a/superset/examples/country_map.py +++ b/superset/examples/country_map.py @@ -39,38 +39,39 @@ def load_country_map_data(only_metadata: bool = False, force: bool = False) -> N """Loading data for map with country map""" tbl_name = "birth_france_by_region" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("birth_france_data_for_country_map.csv") - data = pd.read_csv(url, encoding="utf-8") - data["dttm"] = datetime.datetime.now().date() - data.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "DEPT_ID": String(10), - "2003": BigInteger, - "2004": BigInteger, - "2005": BigInteger, - "2006": BigInteger, - "2007": BigInteger, - "2008": BigInteger, - "2009": BigInteger, - "2010": BigInteger, - "2011": BigInteger, - "2012": BigInteger, - "2013": BigInteger, - "2014": BigInteger, - "dttm": Date(), - }, - index=False, - ) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) + + if not only_metadata and (not table_exists or force): + url = get_example_url("birth_france_data_for_country_map.csv") + data = pd.read_csv(url, encoding="utf-8") + data["dttm"] = datetime.datetime.now().date() + data.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "DEPT_ID": String(10), + "2003": BigInteger, + "2004": BigInteger, + "2005": BigInteger, + "2006": BigInteger, + "2007": BigInteger, + "2008": BigInteger, + "2009": BigInteger, + "2010": BigInteger, + "2011": BigInteger, + "2012": BigInteger, + "2013": BigInteger, + "2014": BigInteger, + "dttm": Date(), + }, + index=False, + ) print("Done loading table!") print("-" * 80) diff --git a/superset/examples/energy.py b/superset/examples/energy.py index 72b22525f2760..6688e5d08844d 100644 --- a/superset/examples/energy.py +++ b/superset/examples/energy.py @@ -41,24 +41,25 @@ def load_energy( """Loads an energy related dataset to use with sankey and graphs""" tbl_name = "energy_usage" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("energy.json.gz") - pdf = pd.read_json(url, compression="gzip") - pdf = pdf.head(100) if sample else pdf - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={"source": String(255), "target": String(255), "value": Float()}, - index=False, - method="multi", - ) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) + + if not only_metadata and (not table_exists or force): + url = get_example_url("energy.json.gz") + pdf = pd.read_json(url, compression="gzip") + pdf = pdf.head(100) if sample else pdf + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={"source": String(255), "target": String(255), "value": Float()}, + index=False, + method="multi", + ) print("Creating table [wb_health_population] reference") table = get_table_connector_registry() diff --git a/superset/examples/flights.py b/superset/examples/flights.py index 1389c65c9a901..7c8f9802988bd 100644 --- a/superset/examples/flights.py +++ b/superset/examples/flights.py @@ -27,35 +27,37 @@ def load_flights(only_metadata: bool = False, force: bool = False) -> None: """Loading random time series data from a zip file in the repo""" tbl_name = "flights" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - flight_data_url = get_example_url("flight_data.csv.gz") - pdf = pd.read_csv(flight_data_url, encoding="latin-1", compression="gzip") + if not only_metadata and (not table_exists or force): + flight_data_url = get_example_url("flight_data.csv.gz") + pdf = pd.read_csv(flight_data_url, encoding="latin-1", compression="gzip") - # Loading airports info to join and get lat/long - airports_url = get_example_url("airports.csv.gz") - airports = pd.read_csv(airports_url, encoding="latin-1", compression="gzip") - airports = airports.set_index("IATA_CODE") + # Loading airports info to join and get lat/long + airports_url = get_example_url("airports.csv.gz") + airports = pd.read_csv(airports_url, encoding="latin-1", compression="gzip") + airports = airports.set_index("IATA_CODE") - pdf[ # pylint: disable=unsupported-assignment-operation,useless-suppression - "ds" - ] = (pdf.YEAR.map(str) + "-0" + pdf.MONTH.map(str) + "-0" + pdf.DAY.map(str)) - pdf.ds = pd.to_datetime(pdf.ds) - pdf.drop(columns=["DAY", "MONTH", "YEAR"]) - pdf = pdf.join(airports, on="ORIGIN_AIRPORT", rsuffix="_ORIG") - pdf = pdf.join(airports, on="DESTINATION_AIRPORT", rsuffix="_DEST") - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={"ds": DateTime}, - index=False, - ) + pdf[ # pylint: disable=unsupported-assignment-operation,useless-suppression + "ds" + ] = ( + pdf.YEAR.map(str) + "-0" + pdf.MONTH.map(str) + "-0" + pdf.DAY.map(str) + ) + pdf.ds = pd.to_datetime(pdf.ds) + pdf.drop(columns=["DAY", "MONTH", "YEAR"]) + pdf = pdf.join(airports, on="ORIGIN_AIRPORT", rsuffix="_ORIG") + pdf = pdf.join(airports, on="DESTINATION_AIRPORT", rsuffix="_DEST") + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={"ds": DateTime}, + index=False, + ) table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() diff --git a/superset/examples/long_lat.py b/superset/examples/long_lat.py index 76f51a615951f..88b45548f48dc 100644 --- a/superset/examples/long_lat.py +++ b/superset/examples/long_lat.py @@ -39,49 +39,51 @@ def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None """Loading lat/long data from a csv file in the repo""" tbl_name = "long_lat" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("san_francisco.csv.gz") - pdf = pd.read_csv(url, encoding="utf-8", compression="gzip") - start = datetime.datetime.now().replace( - hour=0, minute=0, second=0, microsecond=0 - ) - pdf["datetime"] = [ - start + datetime.timedelta(hours=i * 24 / (len(pdf) - 1)) - for i in range(len(pdf)) - ] - pdf["occupancy"] = [random.randint(1, 6) for _ in range(len(pdf))] - pdf["radius_miles"] = [random.uniform(1, 3) for _ in range(len(pdf))] - pdf["geohash"] = pdf[["LAT", "LON"]].apply(lambda x: geohash.encode(*x), axis=1) - pdf["delimited"] = pdf["LAT"].map(str).str.cat(pdf["LON"].map(str), sep=",") - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "longitude": Float(), - "latitude": Float(), - "number": Float(), - "street": String(100), - "unit": String(10), - "city": String(50), - "district": String(50), - "region": String(50), - "postcode": Float(), - "id": String(100), - "datetime": DateTime(), - "occupancy": Float(), - "radius_miles": Float(), - "geohash": String(12), - "delimited": String(60), - }, - index=False, - ) + if not only_metadata and (not table_exists or force): + url = get_example_url("san_francisco.csv.gz") + pdf = pd.read_csv(url, encoding="utf-8", compression="gzip") + start = datetime.datetime.now().replace( + hour=0, minute=0, second=0, microsecond=0 + ) + pdf["datetime"] = [ + start + datetime.timedelta(hours=i * 24 / (len(pdf) - 1)) + for i in range(len(pdf)) + ] + pdf["occupancy"] = [random.randint(1, 6) for _ in range(len(pdf))] + pdf["radius_miles"] = [random.uniform(1, 3) for _ in range(len(pdf))] + pdf["geohash"] = pdf[["LAT", "LON"]].apply( + lambda x: geohash.encode(*x), axis=1 + ) + pdf["delimited"] = pdf["LAT"].map(str).str.cat(pdf["LON"].map(str), sep=",") + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "longitude": Float(), + "latitude": Float(), + "number": Float(), + "street": String(100), + "unit": String(10), + "city": String(50), + "district": String(50), + "region": String(50), + "postcode": Float(), + "id": String(100), + "datetime": DateTime(), + "occupancy": Float(), + "radius_miles": Float(), + "geohash": String(12), + "delimited": String(60), + }, + index=False, + ) print("Done loading table!") print("-" * 80) diff --git a/superset/examples/multiformat_time_series.py b/superset/examples/multiformat_time_series.py index 62e16d2cb0881..b030bcdb0f23c 100644 --- a/superset/examples/multiformat_time_series.py +++ b/superset/examples/multiformat_time_series.py @@ -39,41 +39,41 @@ def load_multiformat_time_series( # pylint: disable=too-many-locals """Loading time series data from a zip file in the repo""" tbl_name = "multiformat_time_series" database = get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("multiformat_time_series.json.gz") - pdf = pd.read_json(url, compression="gzip") - # TODO(bkyryliuk): move load examples data into the pytest fixture - if database.backend == "presto": - pdf.ds = pd.to_datetime(pdf.ds, unit="s") - pdf.ds = pdf.ds.dt.strftime("%Y-%m-%d") - pdf.ds2 = pd.to_datetime(pdf.ds2, unit="s") - pdf.ds2 = pdf.ds2.dt.strftime("%Y-%m-%d %H:%M%:%S") - else: - pdf.ds = pd.to_datetime(pdf.ds, unit="s") - pdf.ds2 = pd.to_datetime(pdf.ds2, unit="s") + if not only_metadata and (not table_exists or force): + url = get_example_url("multiformat_time_series.json.gz") + pdf = pd.read_json(url, compression="gzip") + # TODO(bkyryliuk): move load examples data into the pytest fixture + if database.backend == "presto": + pdf.ds = pd.to_datetime(pdf.ds, unit="s") + pdf.ds = pdf.ds.dt.strftime("%Y-%m-%d") + pdf.ds2 = pd.to_datetime(pdf.ds2, unit="s") + pdf.ds2 = pdf.ds2.dt.strftime("%Y-%m-%d %H:%M%:%S") + else: + pdf.ds = pd.to_datetime(pdf.ds, unit="s") + pdf.ds2 = pd.to_datetime(pdf.ds2, unit="s") - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "ds": String(255) if database.backend == "presto" else Date, - "ds2": String(255) if database.backend == "presto" else DateTime, - "epoch_s": BigInteger, - "epoch_ms": BigInteger, - "string0": String(100), - "string1": String(100), - "string2": String(100), - "string3": String(100), - }, - index=False, - ) + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "ds": String(255) if database.backend == "presto" else Date, + "ds2": String(255) if database.backend == "presto" else DateTime, + "epoch_s": BigInteger, + "epoch_ms": BigInteger, + "string0": String(100), + "string1": String(100), + "string2": String(100), + "string3": String(100), + }, + index=False, + ) print("Done loading table!") print("-" * 80) diff --git a/superset/examples/paris.py b/superset/examples/paris.py index c323007028523..a54a3706b13c0 100644 --- a/superset/examples/paris.py +++ b/superset/examples/paris.py @@ -28,29 +28,29 @@ def load_paris_iris_geojson(only_metadata: bool = False, force: bool = False) -> None: tbl_name = "paris_iris_mapping" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("paris_iris.json.gz") - df = pd.read_json(url, compression="gzip") - df["features"] = df.features.map(json.dumps) + if not only_metadata and (not table_exists or force): + url = get_example_url("paris_iris.json.gz") + df = pd.read_json(url, compression="gzip") + df["features"] = df.features.map(json.dumps) - df.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "color": String(255), - "name": String(255), - "features": Text, - "type": Text, - }, - index=False, - ) + df.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "color": String(255), + "name": String(255), + "features": Text, + "type": Text, + }, + index=False, + ) print("Creating table {} reference".format(tbl_name)) table = get_table_connector_registry() diff --git a/superset/examples/random_time_series.py b/superset/examples/random_time_series.py index 4a2628df7a074..9a296ec2c4713 100644 --- a/superset/examples/random_time_series.py +++ b/superset/examples/random_time_series.py @@ -37,28 +37,28 @@ def load_random_time_series_data( """Loading random time series data from a zip file in the repo""" tbl_name = "random_time_series" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("random_time_series.json.gz") - pdf = pd.read_json(url, compression="gzip") - if database.backend == "presto": - pdf.ds = pd.to_datetime(pdf.ds, unit="s") - pdf.ds = pdf.ds.dt.strftime("%Y-%m-%d %H:%M%:%S") - else: - pdf.ds = pd.to_datetime(pdf.ds, unit="s") + if not only_metadata and (not table_exists or force): + url = get_example_url("random_time_series.json.gz") + pdf = pd.read_json(url, compression="gzip") + if database.backend == "presto": + pdf.ds = pd.to_datetime(pdf.ds, unit="s") + pdf.ds = pdf.ds.dt.strftime("%Y-%m-%d %H:%M%:%S") + else: + pdf.ds = pd.to_datetime(pdf.ds, unit="s") - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={"ds": DateTime if database.backend != "presto" else String(255)}, - index=False, - ) + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={"ds": DateTime if database.backend != "presto" else String(255)}, + index=False, + ) print("Done loading table!") print("-" * 80) diff --git a/superset/examples/sf_population_polygons.py b/superset/examples/sf_population_polygons.py index 71ba34401af92..6011b82b09651 100644 --- a/superset/examples/sf_population_polygons.py +++ b/superset/examples/sf_population_polygons.py @@ -30,29 +30,29 @@ def load_sf_population_polygons( ) -> None: tbl_name = "sf_population_polygons" database = database_utils.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - if not only_metadata and (not table_exists or force): - url = get_example_url("sf_population.json.gz") - df = pd.read_json(url, compression="gzip") - df["contour"] = df.contour.map(json.dumps) + if not only_metadata and (not table_exists or force): + url = get_example_url("sf_population.json.gz") + df = pd.read_json(url, compression="gzip") + df["contour"] = df.contour.map(json.dumps) - df.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=500, - dtype={ - "zipcode": BigInteger, - "population": BigInteger, - "contour": Text, - "area": Float, - }, - index=False, - ) + df.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=500, + dtype={ + "zipcode": BigInteger, + "population": BigInteger, + "contour": Text, + "area": Float, + }, + index=False, + ) print("Creating table {} reference".format(tbl_name)) table = get_table_connector_registry() diff --git a/superset/examples/supported_charts_dashboard.py b/superset/examples/supported_charts_dashboard.py index aa4f404ccb0fe..551741bf7d17b 100644 --- a/superset/examples/supported_charts_dashboard.py +++ b/superset/examples/supported_charts_dashboard.py @@ -453,11 +453,11 @@ def load_supported_charts_dashboard() -> None: """Loading a dashboard featuring supported charts""" database = get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name + with database.get_sqla_engine_with_context() as engine: + schema = inspect(engine).default_schema_name - tbl_name = "birth_names" - table_exists = database.has_table_by_name(tbl_name, schema=schema) + tbl_name = "birth_names" + table_exists = database.has_table_by_name(tbl_name, schema=schema) if table_exists: table = get_table_connector_registry() diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py index 4a18f806eae56..b65ad68d1af62 100644 --- a/superset/examples/world_bank.py +++ b/superset/examples/world_bank.py @@ -51,37 +51,38 @@ def load_world_bank_health_n_pop( # pylint: disable=too-many-locals, too-many-s """Loads the world bank health dataset, slices and a dashboard""" tbl_name = "wb_health_population" database = superset.utils.database.get_example_database() - engine = database.get_sqla_engine() - schema = inspect(engine).default_schema_name - table_exists = database.has_table_by_name(tbl_name) + with database.get_sqla_engine_with_context() as engine: - if not only_metadata and (not table_exists or force): - url = get_example_url("countries.json.gz") - pdf = pd.read_json(url, compression="gzip") - pdf.columns = [col.replace(".", "_") for col in pdf.columns] - if database.backend == "presto": - pdf.year = pd.to_datetime(pdf.year) - pdf.year = pdf.year.dt.strftime("%Y-%m-%d %H:%M%:%S") - else: - pdf.year = pd.to_datetime(pdf.year) - pdf = pdf.head(100) if sample else pdf + schema = inspect(engine).default_schema_name + table_exists = database.has_table_by_name(tbl_name) - pdf.to_sql( - tbl_name, - engine, - schema=schema, - if_exists="replace", - chunksize=50, - dtype={ - # TODO(bkyryliuk): use TIMESTAMP type for presto - "year": DateTime if database.backend != "presto" else String(255), - "country_code": String(3), - "country_name": String(255), - "region": String(255), - }, - method="multi", - index=False, - ) + if not only_metadata and (not table_exists or force): + url = get_example_url("countries.json.gz") + pdf = pd.read_json(url, compression="gzip") + pdf.columns = [col.replace(".", "_") for col in pdf.columns] + if database.backend == "presto": + pdf.year = pd.to_datetime(pdf.year) + pdf.year = pdf.year.dt.strftime("%Y-%m-%d %H:%M%:%S") + else: + pdf.year = pd.to_datetime(pdf.year) + pdf = pdf.head(100) if sample else pdf + + pdf.to_sql( + tbl_name, + engine, + schema=schema, + if_exists="replace", + chunksize=50, + dtype={ + # TODO(bkyryliuk): use TIMESTAMP type for presto + "year": DateTime if database.backend != "presto" else String(255), + "country_code": String(3), + "country_name": String(255), + "region": String(255), + }, + method="multi", + index=False, + ) print("Creating table [wb_health_population] reference") table = get_table_connector_registry() diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 65aaeef26eba8..8c53c4c8e7c32 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -149,10 +149,7 @@ def init_views(self) -> None: from superset.security.api import SecurityRestApi from superset.views.access_requests import AccessRequestsModelView from superset.views.alerts import AlertView, ReportView - from superset.views.annotations import ( - AnnotationLayerModelView, - AnnotationModelView, - ) + from superset.views.annotations import AnnotationLayerView from superset.views.api import Api from superset.views.chart.views import SliceAsync, SliceModelView from superset.views.core import Superset @@ -236,16 +233,6 @@ def init_views(self) -> None: category="Data", category_label=__("Data"), ) - - appbuilder.add_view( - AnnotationLayerModelView, - "Annotation Layers", - label=__("Annotation Layers"), - icon="fa-comment", - category="Manage", - category_label=__("Manage"), - category_icon="", - ) appbuilder.add_view( DashboardModelView, "Dashboards", @@ -323,7 +310,6 @@ def init_views(self) -> None: appbuilder.add_view_no_menu(SliceAsync) appbuilder.add_view_no_menu(SqlLab) appbuilder.add_view_no_menu(SqlMetricInlineView) - appbuilder.add_view_no_menu(AnnotationModelView) appbuilder.add_view_no_menu(Superset) appbuilder.add_view_no_menu(TableColumnInlineView) appbuilder.add_view_no_menu(TableModelView) @@ -401,6 +387,17 @@ def init_views(self) -> None: menu_cond=lambda: feature_flag_manager.is_feature_enabled("ALERT_REPORTS"), ) + appbuilder.add_view( + AnnotationLayerView, + "Annotation Layers", + label=_("Annotation Layers"), + href="/annotationlayer/list/", + icon="fa-comment", + category_icon="", + category="Manage", + category_label=__("Manage"), + ) + appbuilder.add_view( AccessRequestsModelView, "Access requests", diff --git a/superset/migrations/versions/2022-04-01_14-38_a9422eeaae74_new_dataset_models_take_2.py b/superset/migrations/versions/2022-04-01_14-38_a9422eeaae74_new_dataset_models_take_2.py index 87c7e13849abd..2dcd1650f0efa 100644 --- a/superset/migrations/versions/2022-04-01_14-38_a9422eeaae74_new_dataset_models_take_2.py +++ b/superset/migrations/versions/2022-04-01_14-38_a9422eeaae74_new_dataset_models_take_2.py @@ -636,14 +636,31 @@ def postprocess_columns(session: Session) -> None: return def get_joined_tables(offset, limit): + + # Import aliased from sqlalchemy + from sqlalchemy.orm import aliased + + # Create alias of NewColumn + new_column_alias = aliased(NewColumn) + # Get subquery and give it the alias "sl_colums_2" + subquery = ( + session.query(new_column_alias) + .offset(offset) + .limit(limit) + .subquery("sl_columns_2") + ) + return ( sa.join( - session.query(NewColumn) - .offset(offset) - .limit(limit) - .subquery("sl_columns"), + subquery, + NewColumn, + # Use column id from subquery + subquery.c.id == NewColumn.id, + ) + .join( dataset_column_association_table, - dataset_column_association_table.c.column_id == NewColumn.id, + # Use column id from subquery + dataset_column_association_table.c.column_id == subquery.c.id, ) .join( NewDataset, @@ -661,12 +678,14 @@ def get_joined_tables(offset, limit): .join(Database, Database.id == NewDataset.database_id) .join( TableColumn, - TableColumn.uuid == NewColumn.uuid, + # Use column uuid from subquery + TableColumn.uuid == subquery.c.uuid, isouter=True, ) .join( SqlMetric, - SqlMetric.uuid == NewColumn.uuid, + # Use column uuid from subquery + SqlMetric.uuid == subquery.c.uuid, isouter=True, ) ) diff --git a/superset/migrations/versions/2022-11-28_17-51_4ce1d9b25135_remove_filter_bar_orientation.py b/superset/migrations/versions/2022-11-28_17-51_4ce1d9b25135_remove_filter_bar_orientation.py new file mode 100644 index 0000000000000..07ee47b9845c8 --- /dev/null +++ b/superset/migrations/versions/2022-11-28_17-51_4ce1d9b25135_remove_filter_bar_orientation.py @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""remove_filter_bar_orientation + +Revision ID: 4ce1d9b25135 +Revises: deb4c9d4a4ef +Create Date: 2022-11-28 17:51:08.954439 + +""" + +# revision identifiers, used by Alembic. +revision = "4ce1d9b25135" +down_revision = "deb4c9d4a4ef" + +import json + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.ext.declarative import declarative_base + +from superset import db + +Base = declarative_base() + + +class Dashboard(Base): + __tablename__ = "dashboards" + id = sa.Column(sa.Integer, primary_key=True) + json_metadata = sa.Column(sa.Text) + + +def upgrade(): + pass + + +def downgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + + dashboards = ( + session.query(Dashboard) + .filter(Dashboard.json_metadata.like('%"filter_bar_orientation"%')) + .all() + ) + for dashboard in dashboards: + json_meta = json.loads(dashboard.json_metadata) + filter_bar_orientation = json_meta.pop("filter_bar_orientation", None) + if filter_bar_orientation: + dashboard.json_metadata = json.dumps(json_meta) + session.commit() + session.close() diff --git a/superset/models/core.py b/superset/models/core.py index 86b9eb1bde759..4efcf311c62ac 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -369,12 +369,9 @@ def get_sqla_engine_with_context( nullpool: bool = True, source: Optional[utils.QuerySource] = None, ) -> Engine: - try: - yield self.get_sqla_engine(schema=schema, nullpool=nullpool, source=source) - except Exception as ex: - raise self.db_engine_spec.get_dbapi_mapped_exception(ex) + yield self._get_sqla_engine(schema=schema, nullpool=nullpool, source=source) - def get_sqla_engine( + def _get_sqla_engine( self, schema: Optional[str] = None, nullpool: bool = True, @@ -392,7 +389,7 @@ def get_sqla_engine( ) masked_url = self.get_password_masked_url(sqlalchemy_url) - logger.debug("Database.get_sqla_engine(). Masked URL: %s", str(masked_url)) + logger.debug("Database._get_sqla_engine(). Masked URL: %s", str(masked_url)) params = extra.get("engine_params", {}) if nullpool: @@ -415,7 +412,7 @@ def get_sqla_engine( source = utils.QuerySource.DASHBOARD elif "/explore/" in request.referrer: source = utils.QuerySource.CHART - elif "/superset/sqllab/" in request.referrer: + elif "/superset/sqllab" in request.referrer: source = utils.QuerySource.SQL_LAB sqlalchemy_url, params = DB_CONNECTION_MUTATOR( @@ -442,7 +439,7 @@ def get_df( # pylint: disable=too-many-locals mutator: Optional[Callable[[pd.DataFrame], None]] = None, ) -> pd.DataFrame: sqls = self.db_engine_spec.parse_sql(sql) - engine = self.get_sqla_engine(schema) + engine = self._get_sqla_engine(schema) def needs_conversion(df_series: pd.Series) -> bool: return ( @@ -487,7 +484,7 @@ def _log_query(sql: str) -> None: return df def compile_sqla_query(self, qry: Select, schema: Optional[str] = None) -> str: - engine = self.get_sqla_engine(schema=schema) + engine = self._get_sqla_engine(schema=schema) sql = str(qry.compile(engine, compile_kwargs={"literal_binds": True})) @@ -508,7 +505,7 @@ def select_star( # pylint: disable=too-many-arguments cols: Optional[List[Dict[str, Any]]] = None, ) -> str: """Generates a ``select *`` statement in the proper dialect""" - eng = self.get_sqla_engine(schema=schema, source=utils.QuerySource.SQL_LAB) + eng = self._get_sqla_engine(schema=schema, source=utils.QuerySource.SQL_LAB) return self.db_engine_spec.select_star( self, table_name, @@ -533,7 +530,7 @@ def safe_sqlalchemy_uri(self) -> str: @property def inspector(self) -> Inspector: - engine = self.get_sqla_engine() + engine = self._get_sqla_engine() return sqla.inspect(engine) @cache_util.memoized_func( @@ -546,7 +543,7 @@ def get_all_table_names_in_schema( # pylint: disable=unused-argument cache: bool = False, cache_timeout: Optional[int] = None, force: bool = False, - ) -> List[Tuple[str, str]]: + ) -> Set[Tuple[str, str]]: """Parameters need to be passed as keyword arguments. For unused parameters, they are referenced in @@ -556,13 +553,17 @@ def get_all_table_names_in_schema( # pylint: disable=unused-argument :param cache: whether cache is enabled for the function :param cache_timeout: timeout in seconds for the cache :param force: whether to force refresh the cache - :return: list of tables + :return: The table/schema pairs """ try: - tables = self.db_engine_spec.get_table_names( - database=self, inspector=self.inspector, schema=schema - ) - return [(table, schema) for table in tables] + return { + (table, schema) + for table in self.db_engine_spec.get_table_names( + database=self, + inspector=self.inspector, + schema=schema, + ) + } except Exception as ex: raise self.db_engine_spec.get_dbapi_mapped_exception(ex) @@ -576,7 +577,7 @@ def get_all_view_names_in_schema( # pylint: disable=unused-argument cache: bool = False, cache_timeout: Optional[int] = None, force: bool = False, - ) -> List[Tuple[str, str]]: + ) -> Set[Tuple[str, str]]: """Parameters need to be passed as keyword arguments. For unused parameters, they are referenced in @@ -586,13 +587,17 @@ def get_all_view_names_in_schema( # pylint: disable=unused-argument :param cache: whether cache is enabled for the function :param cache_timeout: timeout in seconds for the cache :param force: whether to force refresh the cache - :return: list of views + :return: set of views """ try: - views = self.db_engine_spec.get_view_names( - database=self, inspector=self.inspector, schema=schema - ) - return [(view, schema) for view in views] + return { + (view, schema) + for view in self.db_engine_spec.get_view_names( + database=self, + inspector=self.inspector, + schema=schema, + ) + } except Exception as ex: raise self.db_engine_spec.get_dbapi_mapped_exception(ex) @@ -674,7 +679,7 @@ def get_table(self, table_name: str, schema: Optional[str] = None) -> Table: meta, schema=schema or None, autoload=True, - autoload_with=self.get_sqla_engine(), + autoload_with=self._get_sqla_engine(), ) def get_table_comment( @@ -765,11 +770,11 @@ def get_perm(self) -> str: return self.perm # type: ignore def has_table(self, table: Table) -> bool: - engine = self.get_sqla_engine() + engine = self._get_sqla_engine() return engine.has_table(table.table_name, table.schema or None) def has_table_by_name(self, table_name: str, schema: Optional[str] = None) -> bool: - engine = self.get_sqla_engine() + engine = self._get_sqla_engine() return engine.has_table(table_name, schema) @classmethod @@ -788,7 +793,7 @@ def _has_view( return view_name in view_names def has_view(self, view_name: str, schema: Optional[str] = None) -> bool: - engine = self.get_sqla_engine() + engine = self._get_sqla_engine() return engine.run_callable(self._has_view, engine.dialect, view_name, schema) def has_view_by_name(self, view_name: str, schema: Optional[str] = None) -> bool: diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 57567e61641c4..a98d76e58162e 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -224,8 +224,9 @@ def charts(self) -> List[str]: @property def sqla_metadata(self) -> None: # pylint: disable=no-member - meta = MetaData(bind=self.get_sqla_engine()) - meta.reflect() + with self.get_sqla_engine_with_context() as engine: + meta = MetaData(bind=engine) + meta.reflect() @property def status(self) -> utils.DashboardStatus: diff --git a/superset/models/filter_set.py b/superset/models/filter_set.py index 2d3b218793dcf..4bbef264900d6 100644 --- a/superset/models/filter_set.py +++ b/superset/models/filter_set.py @@ -55,8 +55,9 @@ def url(self) -> str: @property def sqla_metadata(self) -> None: # pylint: disable=no-member - meta = MetaData(bind=self.get_sqla_engine()) - meta.reflect() + with self.get_sqla_engine_with_context() as engine: + meta = MetaData(bind=engine) + meta.reflect() @property def changed_by_name(self) -> str: diff --git a/superset/models/helpers.py b/superset/models/helpers.py index cb314de80275c..2582f7e8d2b60 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -1095,6 +1095,7 @@ def template_params_dict(self) -> Dict[Any, Any]: @staticmethod def filter_values_handler( # pylint: disable=too-many-arguments values: Optional[FilterValues], + operator: str, target_generic_type: utils.GenericDataType, target_native_type: Optional[str] = None, is_list_target: bool = False, @@ -1107,6 +1108,8 @@ def filter_values_handler( # pylint: disable=too-many-arguments return None def handle_single_value(value: Optional[FilterValue]) -> Optional[FilterValue]: + if operator == utils.FilterOperator.TEMPORAL_RANGE: + return value if ( isinstance(value, (float, int)) and target_generic_type == utils.GenericDataType.TEMPORAL @@ -1281,13 +1284,13 @@ def values_for_column(self, column_name: str, limit: int = 10000) -> List[Any]: if limit: qry = qry.limit(limit) - engine = self.database.get_sqla_engine() # type: ignore - sql = qry.compile(engine, compile_kwargs={"literal_binds": True}) - sql = self._apply_cte(sql, cte) - sql = self.mutate_query_from_config(sql) + with self.database.get_sqla_engine_with_context() as engine: # type: ignore + sql = qry.compile(engine, compile_kwargs={"literal_binds": True}) + sql = self._apply_cte(sql, cte) + sql = self.mutate_query_from_config(sql) - df = pd.read_sql_query(sql=sql, con=engine) - return df[column_name].to_list() + df = pd.read_sql_query(sql=sql, con=engine) + return df[column_name].to_list() def get_timestamp_expression( self, @@ -1692,6 +1695,7 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma target_generic_type = utils.GenericDataType.STRING eq = self.filter_values_handler( values=val, + operator=op, target_generic_type=target_generic_type, target_native_type=col_type, is_list_target=is_list_target, diff --git a/superset/reports/commands/alert.py b/superset/reports/commands/alert.py index 88ece787d378a..255280704e78f 100644 --- a/superset/reports/commands/alert.py +++ b/superset/reports/commands/alert.py @@ -25,7 +25,7 @@ from celery.exceptions import SoftTimeLimitExceeded from flask_babel import lazy_gettext as _ -from superset import app, jinja_context, security_manager +from superset import app, jinja_context from superset.commands.base import BaseCommand from superset.reports.commands.exceptions import ( AlertQueryError, @@ -36,6 +36,7 @@ AlertValidatorConfigError, ) from superset.reports.models import ReportSchedule, ReportScheduleValidatorType +from superset.reports.utils import get_executor from superset.utils.core import override_user from superset.utils.retries import retry_call @@ -148,11 +149,8 @@ def _execute_query(self) -> pd.DataFrame: rendered_sql, ALERT_SQL_LIMIT ) - with override_user( - security_manager.find_user( - username=app.config["THUMBNAIL_SELENIUM_USER"] - ) - ): + user = get_executor(self._report_schedule) + with override_user(user): start = default_timer() df = self._report_schedule.database.get_df(sql=limited_rendered_sql) stop = default_timer() diff --git a/superset/sql_lab.py b/superset/sql_lab.py index 96afc7f51ed90..6d9903c8f0009 100644 --- a/superset/sql_lab.py +++ b/superset/sql_lab.py @@ -463,61 +463,66 @@ def execute_sql_statements( # pylint: disable=too-many-arguments, too-many-loca ) ) - engine = database.get_sqla_engine(query.schema, source=QuerySource.SQL_LAB) - # Sharing a single connection and cursor across the - # execution of all statements (if many) - with closing(engine.raw_connection()) as conn: - # closing the connection closes the cursor as well - cursor = conn.cursor() - cancel_query_id = db_engine_spec.get_cancel_query_id(cursor, query) - if cancel_query_id is not None: - query.set_extra_json_key(cancel_query_key, cancel_query_id) - session.commit() - statement_count = len(statements) - for i, statement in enumerate(statements): - # Check if stopped - session.refresh(query) - if query.status == QueryStatus.STOPPED: - payload.update({"status": query.status}) - return payload - - # For CTAS we create the table only on the last statement - apply_ctas = query.select_as_cta and ( - query.ctas_method == CtasMethod.VIEW - or (query.ctas_method == CtasMethod.TABLE and i == len(statements) - 1) - ) - - # Run statement - msg = f"Running statement {i+1} out of {statement_count}" - logger.info("Query %s: %s", str(query_id), msg) - query.set_extra_json_key("progress", msg) - session.commit() - try: - result_set = execute_sql_statement( - statement, - query, - session, - cursor, - log_params, - apply_ctas, - ) - except SqlLabQueryStoppedException: - payload.update({"status": QueryStatus.STOPPED}) - return payload - except Exception as ex: # pylint: disable=broad-except - msg = str(ex) - prefix_message = ( - f"[Statement {i+1} out of {statement_count}]" - if statement_count > 1 - else "" + with database.get_sqla_engine_with_context( + query.schema, source=QuerySource.SQL_LAB + ) as engine: + # Sharing a single connection and cursor across the + # execution of all statements (if many) + with closing(engine.raw_connection()) as conn: + # closing the connection closes the cursor as well + cursor = conn.cursor() + cancel_query_id = db_engine_spec.get_cancel_query_id(cursor, query) + if cancel_query_id is not None: + query.set_extra_json_key(cancel_query_key, cancel_query_id) + session.commit() + statement_count = len(statements) + for i, statement in enumerate(statements): + # Check if stopped + session.refresh(query) + if query.status == QueryStatus.STOPPED: + payload.update({"status": query.status}) + return payload + + # For CTAS we create the table only on the last statement + apply_ctas = query.select_as_cta and ( + query.ctas_method == CtasMethod.VIEW + or ( + query.ctas_method == CtasMethod.TABLE + and i == len(statements) - 1 + ) ) - payload = handle_query_error( - ex, query, session, payload, prefix_message - ) - return payload - # Commit the connection so CTA queries will create the table. - conn.commit() + # Run statement + msg = f"Running statement {i+1} out of {statement_count}" + logger.info("Query %s: %s", str(query_id), msg) + query.set_extra_json_key("progress", msg) + session.commit() + try: + result_set = execute_sql_statement( + statement, + query, + session, + cursor, + log_params, + apply_ctas, + ) + except SqlLabQueryStoppedException: + payload.update({"status": QueryStatus.STOPPED}) + return payload + except Exception as ex: # pylint: disable=broad-except + msg = str(ex) + prefix_message = ( + f"[Statement {i+1} out of {statement_count}]" + if statement_count > 1 + else "" + ) + payload = handle_query_error( + ex, query, session, payload, prefix_message + ) + return payload + + # Commit the connection so CTA queries will create the table. + conn.commit() # Success, updating the query entry in database query.rows = result_set.size @@ -622,10 +627,11 @@ def cancel_query(query: Query) -> bool: if cancel_query_id is None: return False - engine = query.database.get_sqla_engine(query.schema, source=QuerySource.SQL_LAB) - - with closing(engine.raw_connection()) as conn: - with closing(conn.cursor()) as cursor: - return query.database.db_engine_spec.cancel_query( - cursor, query, cancel_query_id - ) + with query.database.get_sqla_engine_with_context( + query.schema, source=QuerySource.SQL_LAB + ) as engine: + with closing(engine.raw_connection()) as conn: + with closing(conn.cursor()) as cursor: + return query.database.db_engine_spec.cancel_query( + cursor, query, cancel_query_id + ) diff --git a/superset/sql_validators/presto_db.py b/superset/sql_validators/presto_db.py index 70b324c900736..37375e484dec5 100644 --- a/superset/sql_validators/presto_db.py +++ b/superset/sql_validators/presto_db.py @@ -162,16 +162,18 @@ def validate( statements = parsed_query.get_statements() logger.info("Validating %i statement(s)", len(statements)) - engine = database.get_sqla_engine(schema, source=QuerySource.SQL_LAB) - # Sharing a single connection and cursor across the - # execution of all statements (if many) - annotations: List[SQLValidationAnnotation] = [] - with closing(engine.raw_connection()) as conn: - cursor = conn.cursor() - for statement in parsed_query.get_statements(): - annotation = cls.validate_statement(statement, database, cursor) - if annotation: - annotations.append(annotation) - logger.debug("Validation found %i error(s)", len(annotations)) + with database.get_sqla_engine_with_context( + schema, source=QuerySource.SQL_LAB + ) as engine: + # Sharing a single connection and cursor across the + # execution of all statements (if many) + annotations: List[SQLValidationAnnotation] = [] + with closing(engine.raw_connection()) as conn: + cursor = conn.cursor() + for statement in parsed_query.get_statements(): + annotation = cls.validate_statement(statement, database, cursor) + if annotation: + annotations.append(annotation) + logger.debug("Validation found %i error(s)", len(annotations)) return annotations diff --git a/superset/templates/superset/form_view/database_schemas_selector.html b/superset/templates/superset/form_view/database_schemas_selector.html index 73955d0174e6c..b9efb68d7e5f7 100644 --- a/superset/templates/superset/form_view/database_schemas_selector.html +++ b/superset/templates/superset/form_view/database_schemas_selector.html @@ -17,7 +17,7 @@ under the License. #}