mirror of
https://github.com/apache/superset.git
synced 2026-08-25 09:31:16 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ddafa6b5 |
@@ -1138,8 +1138,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
?.backgroundColor || backgroundColor;
|
||||
arrow =
|
||||
column.label === comparisonLabels[0]
|
||||
? (basicColorColumnFormatters[row.index]?.[column.key]
|
||||
?.mainArrow ?? arrow)
|
||||
? basicColorColumnFormatters[row.index]?.[column.key]?.mainArrow
|
||||
: '';
|
||||
}
|
||||
const rowSurfaceColor =
|
||||
@@ -1210,18 +1209,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
basicColorColumnFormatters &&
|
||||
basicColorColumnFormatters?.length > 0
|
||||
) {
|
||||
const columnArrowColor =
|
||||
basicColorColumnFormatters[row.index]?.[column.key]?.arrowColor;
|
||||
if (columnArrowColor) {
|
||||
arrowStyles = css`
|
||||
color: ${
|
||||
columnArrowColor === ColorSchemeEnum.Green
|
||||
? theme.colorSuccess
|
||||
: theme.colorError
|
||||
};
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
`;
|
||||
}
|
||||
arrowStyles = css`
|
||||
color: ${
|
||||
basicColorColumnFormatters[row.index]?.[column.key]
|
||||
?.arrowColor === ColorSchemeEnum.Green
|
||||
? theme.colorSuccess
|
||||
: theme.colorError
|
||||
};
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
`;
|
||||
}
|
||||
|
||||
const cellProps = {
|
||||
|
||||
@@ -2125,18 +2125,8 @@ describe('plugin-chart-table', () => {
|
||||
'rgba(0, 150, 0, 0.2)',
|
||||
);
|
||||
|
||||
// the row missing a formatter entry falls back to the row-level
|
||||
// comparison arrow instead of losing it: before the fix, this row's
|
||||
// arrow was silently cleared (and its color, computed the same way,
|
||||
// would have flipped to the "decrease" color) whenever the
|
||||
// column-specific lookup for this row was undefined.
|
||||
const arrowCell = screen
|
||||
.getAllByTitle('110')
|
||||
.find(cell => cell.querySelector('span'));
|
||||
expect(arrowCell).toHaveTextContent('↑110');
|
||||
expect(getComputedStyle(arrowCell!).background).toContain(
|
||||
'rgba(0, 150, 0, 0.2)',
|
||||
);
|
||||
// the row missing a formatter entry still renders its raw value
|
||||
expect(screen.getAllByTitle('110').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('preserves client-side search text across temporal table rerenders', async () => {
|
||||
|
||||
@@ -96,29 +96,19 @@ test('a malformed window does not leak into the copy', () => {
|
||||
test('the confirm copy quotes the window when there is one', () => {
|
||||
withConf({ SOFT_DELETE_RETENTION_DAYS: 30 });
|
||||
expect(archiveConfirmDescription('chart')).toBe(
|
||||
'This chart will be moved to Recently Archived in the Settings menu. You can recover it there within 30 days.',
|
||||
'This chart will be moved to Recently Archived. You can recover it there within 30 days.',
|
||||
);
|
||||
expect(archiveConfirmDescription('charts', true)).toBe(
|
||||
'These charts will be moved to Recently Archived in the Settings menu. You can recover them there within 30 days.',
|
||||
);
|
||||
});
|
||||
|
||||
test('a one-day window is quoted in the singular', () => {
|
||||
withConf({ SOFT_DELETE_RETENTION_DAYS: 1 });
|
||||
expect(archiveConfirmDescription('chart')).toBe(
|
||||
'This chart will be moved to Recently Archived in the Settings menu. You can recover it there within 1 day.',
|
||||
);
|
||||
expect(archiveConfirmDescription('charts', true)).toBe(
|
||||
'These charts will be moved to Recently Archived in the Settings menu. You can recover them there within 1 day.',
|
||||
'These charts will be moved to Recently Archived. You can recover them there within 30 days.',
|
||||
);
|
||||
});
|
||||
|
||||
test('the confirm copy omits the clause when there is no window', () => {
|
||||
withConf({});
|
||||
expect(archiveConfirmDescription('dashboard')).toBe(
|
||||
'This dashboard will be moved to Recently Archived in the Settings menu. You can recover it there.',
|
||||
'This dashboard will be moved to Recently Archived. You can recover it there.',
|
||||
);
|
||||
expect(archiveConfirmDescription('dashboards', true)).toBe(
|
||||
'These dashboards will be moved to Recently Archived in the Settings menu. You can recover them there.',
|
||||
'These dashboards will be moved to Recently Archived. You can recover them there.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { escape } from 'lodash-es';
|
||||
import { t, tn } from '@apache-superset/core/translation';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
|
||||
@@ -62,32 +62,25 @@ export function archiveConfirmDescription(
|
||||
// Each case is a single, complete translation unit (rather than two joined
|
||||
// fragments) so translators control the whole sentence; only the noun and the
|
||||
// day count are interpolated, matching Superset's existing `%(...)s` usage.
|
||||
// The timed variants pluralize on the day count (`tn`) because the retention
|
||||
// window accepts 1: "within 1 days" is exactly the copy defect this module
|
||||
// exists to prevent.
|
||||
const days = getSoftDeleteRetentionDays();
|
||||
if (days) {
|
||||
return plural
|
||||
? tn(
|
||||
'These %(type)s will be moved to Recently Archived in the Settings menu. You can recover them there within %(days)s day.',
|
||||
'These %(type)s will be moved to Recently Archived in the Settings menu. You can recover them there within %(days)s days.',
|
||||
days,
|
||||
? t(
|
||||
'These %(type)s will be moved to Recently Archived. You can recover them there within %(days)s days.',
|
||||
{ type: typeLabel, days },
|
||||
)
|
||||
: tn(
|
||||
'This %(type)s will be moved to Recently Archived in the Settings menu. You can recover it there within %(days)s day.',
|
||||
'This %(type)s will be moved to Recently Archived in the Settings menu. You can recover it there within %(days)s days.',
|
||||
days,
|
||||
: t(
|
||||
'This %(type)s will be moved to Recently Archived. You can recover it there within %(days)s days.',
|
||||
{ type: typeLabel, days },
|
||||
);
|
||||
}
|
||||
return plural
|
||||
? t(
|
||||
'These %(type)s will be moved to Recently Archived in the Settings menu. You can recover them there.',
|
||||
'These %(type)s will be moved to Recently Archived. You can recover them there.',
|
||||
{ type: typeLabel },
|
||||
)
|
||||
: t(
|
||||
'This %(type)s will be moved to Recently Archived in the Settings menu. You can recover it there.',
|
||||
'This %(type)s will be moved to Recently Archived. You can recover it there.',
|
||||
{ type: typeLabel },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1316,6 +1316,10 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
||||
try:
|
||||
TestConnectionDatabaseCommand(item).run()
|
||||
return self.response(200, message="OK")
|
||||
except OAuth2RedirectError:
|
||||
# OAuth2 connections pass, so they can be saved. A user later
|
||||
# can then store an OAuth2 token.
|
||||
return self.response(200, message="OK")
|
||||
except (
|
||||
SSHTunnelingNotEnabledError,
|
||||
SSHTunnelDatabasePortError,
|
||||
|
||||
@@ -13831,18 +13831,14 @@ msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"These %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover them there within %(days)s day."
|
||||
msgid_plural ""
|
||||
"These %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover them there within %(days)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"These %(type)s will be moved to Recently Archived. You can recover them "
|
||||
"there within %(days)s days."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"These %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover them there."
|
||||
"These %(type)s will be moved to Recently Archived. You can recover them "
|
||||
"there."
|
||||
msgstr ""
|
||||
|
||||
msgid "These are the datasets this filter will be applied to."
|
||||
@@ -13850,18 +13846,14 @@ msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover it there within %(days)s day."
|
||||
msgid_plural ""
|
||||
"This %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover it there within %(days)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"This %(type)s will be moved to Recently Archived. You can recover it "
|
||||
"there within %(days)s days."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This %(type)s will be moved to Recently Archived in the Settings menu. "
|
||||
"You can recover it there."
|
||||
"This %(type)s will be moved to Recently Archived. You can recover it "
|
||||
"there."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
|
||||
@@ -2403,6 +2403,54 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
assert rv.status_code == 200
|
||||
assert rv.headers["Content-Type"] == "application/json; charset=utf-8"
|
||||
|
||||
@with_config({"PREVENT_UNSAFE_DB_CONNECTIONS": False})
|
||||
def test_test_connection_oauth2(self):
|
||||
"""
|
||||
Database API: Test test connection flow with a connection authenticated via
|
||||
OAuth2.
|
||||
|
||||
The test would always raise ``OAuth2RedirectError``, and we can't start the
|
||||
OAuth2 dance before the connection is saved, so it should return a 200 status.
|
||||
"""
|
||||
self.login(ADMIN_USERNAME)
|
||||
example_db = get_example_database()
|
||||
masked_encrypted_extra = json.dumps(
|
||||
{
|
||||
"oauth2_client_info": {
|
||||
"id": "client_id",
|
||||
"secret": "client_secret",
|
||||
"scope": "some-scope",
|
||||
"authorization_request_uri": "https://example.org/authorize",
|
||||
"token_request_uri": "https://example.org/token",
|
||||
}
|
||||
}
|
||||
)
|
||||
data = {
|
||||
"database_name": "examples",
|
||||
"masked_encrypted_extra": masked_encrypted_extra,
|
||||
"impersonate_user": True,
|
||||
"sqlalchemy_uri": example_db.safe_sqlalchemy_uri(),
|
||||
"server_cert": None,
|
||||
}
|
||||
url = "api/v1/database/test_connection/"
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"superset.commands.database.test_connection.ping",
|
||||
side_effect=Exception("Unauthorized"),
|
||||
),
|
||||
mock.patch.object(
|
||||
example_db.db_engine_spec,
|
||||
"needs_oauth2",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
rv = self.post_assert_metric(url, data, "test_connection")
|
||||
|
||||
assert rv.status_code == 200
|
||||
assert rv.headers["Content-Type"] == "application/json; charset=utf-8"
|
||||
assert json.loads(rv.data.decode("utf-8")) == {"message": "OK"}
|
||||
|
||||
def test_test_connection_failed(self):
|
||||
"""
|
||||
Database API: Test test connection failed
|
||||
|
||||
Reference in New Issue
Block a user