Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/apache/superset into chor…
Browse files Browse the repository at this point in the history
…e/redirect_to_login
  • Loading branch information
geido committed Dec 8, 2021
2 parents 7179377 + 8c25f2f commit a23a886
Show file tree
Hide file tree
Showing 33 changed files with 150,662 additions and 73,985 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/superset-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,4 @@ jobs:
pip-upgrade
pip install -r requirements/base.txt
- name: Test babel extraction
run: |
flask fab babel-extract --target superset/translations \
--output superset/translations/messages.pot \
--config superset/translations/babel.cfg -k _,__,t,tn,tct
run: ./scripts/babel_update.sh
15 changes: 4 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,22 +1039,15 @@ LANGUAGES = {
}
```

### Extracting new strings for translation

```bash
pybabel extract -F superset/translations/babel.cfg -o superset/translations/messages.pot -k _ -k __ -k t -k tn -k tct .
```

This will update the template file `superset/translations/messages.pot` with current application strings. Do not forget to update
this file with the appropriate license information.

### Updating language files

```bash
pybabel update -i superset/translations/messages.pot -d superset/translations --ignore-obsolete
./scripts/babel_update.sh
```

This will update language files with the new extracted strings.
This script will
1. update the template file `superset/translations/messages.pot` with current application strings.
2. update language files with the new extracted strings.

You can then translate the strings gathered in files located under
`superset/translation`, where there's one per language. You can use [Poedit](https://poedit.net/features)
Expand Down
1 change: 1 addition & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ assists people when migrating to a new version.

### Other

- [17589](https://github.com/apache/incubator-superset/pull/17589): It is now possible to limit access to users' recent activity data by setting the `ENABLE_BROAD_ACTIVITY_ACCESS` config flag to false, or customizing the `raise_for_user_activity_access` method in the security manager.
- [16809](https://github.com/apache/incubator-superset/pull/16809): When building the superset frontend assets manually, you should now use Node 16 (previously Node 14 was required/recommended). Node 14 will most likely still work for at least some time, but is no longer actively tested for on CI.
- [17536](https://github.com/apache/superset/pull/17536): introduced a key-value endpoint to store dashboard filter state. This endpoint is backed by Flask-Caching and the default configuration assumes that the values will be stored in the file system. If you are already using another cache backend like Redis or Memchached, you'll probably want to change this setting in `superset_config.py`. The key is `FILTER_STATE_CACHE_CONFIG` and the available settings can be found in Flask-Caching [docs](https://flask-caching.readthedocs.io/en/latest/).

Expand Down
12 changes: 9 additions & 3 deletions scripts/babel_extract.sh → scripts/babel_update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pybabel extract \
--copyright-holder=Superset \
--project=Superset \
-k _ -k __ -k t -k tn -k tct .
cat $LICENSE_TMP $ROOT_DIR/superset/translations/messages.pot > messages.pot.tmp \
&& mv messages.pot.tmp $ROOT_DIR/superset/translations/messages.pot \
&& cd $CURRENT_DIR
cat $LICENSE_TMP superset/translations/messages.pot > messages.pot.tmp \
&& mv messages.pot.tmp superset/translations/messages.pot

pybabel update \
-i superset/translations/messages.pot \
-d superset/translations \
--ignore-obsolete

cd $CURRENT_DIR
32 changes: 30 additions & 2 deletions superset-frontend/src/components/TableLoader/TableLoader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ import { storeWithState } from 'spec/fixtures/mockStore';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
import TableLoader, { TableLoaderProps } from '.';

fetchMock.get('glob:*/api/v1/mock', [
const NO_DATA_TEXT = 'No data available';
const MOCK_GLOB = 'glob:*/api/v1/mock';

fetchMock.get(MOCK_GLOB, [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
]);

const defaultProps: TableLoaderProps = {
dataEndpoint: '/api/v1/mock',
addDangerToast: jest.fn(),
noDataText: NO_DATA_TEXT,
};

function renderWithProps(props: TableLoaderProps = defaultProps) {
Expand Down Expand Up @@ -83,12 +87,36 @@ test('renders with mutator', async () => {
expect(await screen.findAllByRole('heading', { level: 4 })).toHaveLength(2);
});

test('renders empty message', async () => {
fetchMock.mock(MOCK_GLOB, [], {
overwriteRoutes: true,
});

renderWithProps();

expect(await screen.findByText('No data available')).toBeInTheDocument();
});

test('renders blocked message', async () => {
fetchMock.mock(MOCK_GLOB, 403, {
overwriteRoutes: true,
});

renderWithProps();

expect(
await screen.findByText('Access to user activity data is restricted'),
).toBeInTheDocument();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});

test('renders error message', async () => {
fetchMock.mock('glob:*/api/v1/mock', 500, {
fetchMock.mock(MOCK_GLOB, 500, {
overwriteRoutes: true,
});

renderWithProps();

expect(await screen.findByText(NO_DATA_TEXT)).toBeInTheDocument();
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
17 changes: 14 additions & 3 deletions superset-frontend/src/components/TableLoader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ export interface TableLoaderProps {
dataEndpoint?: string;
mutator?: (data: JsonObject) => any[];
columns?: string[];
noDataText?: string;
addDangerToast(text: string): any;
}

const TableLoader = (props: TableLoaderProps) => {
const [data, setData] = useState<Array<any>>([]);
const [isLoading, setIsLoading] = useState(true);
const [isBlocked, setIsBlocked] = useState(false);

useEffect(() => {
const { dataEndpoint, mutator } = props;
Expand All @@ -41,16 +43,22 @@ const TableLoader = (props: TableLoaderProps) => {
.then(({ json }) => {
const data = (mutator ? mutator(json) : json) as Array<any>;
setData(data);
setIsBlocked(false);
setIsLoading(false);
})
.catch(() => {
.catch(response => {
setIsLoading(false);
props.addDangerToast(t('An error occurred'));
if (response.status === 403) {
setIsBlocked(true);
} else {
setIsBlocked(false);
props.addDangerToast(t('An error occurred'));
}
});
}
}, [props]);

const { columns, ...tableProps } = props;
const { columns, noDataText, ...tableProps } = props;

const memoizedColumns = useMemo(() => {
let tableColumns = columns;
Expand Down Expand Up @@ -79,6 +87,9 @@ const TableLoader = (props: TableLoaderProps) => {
pageSize={50}
loading={isLoading}
emptyWrapperType={EmptyWrapperType.Small}
noDataText={
isBlocked ? t('Access to user activity data is restricted') : noDataText
}
{...tableProps}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion superset/charts/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ class ChartDataProphetOptionsSchema(ChartDataPostProcessingOperationOptionsSchem
)
periods = fields.Integer(
descrption="Time periods (in units of `time_grain`) to predict into the future",
min=1,
min=0,
example=7,
required=True,
)
Expand Down
4 changes: 4 additions & 0 deletions superset/common/query_context_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ def processing_time_offsets( # pylint: disable=too-many-locals
def get_data(self, df: pd.DataFrame) -> Union[str, List[Dict[str, Any]]]:
if self._query_context.result_format == ChartDataResultFormat.CSV:
include_index = not isinstance(df.index, pd.RangeIndex)
columns = list(df.columns)
verbose_map = self._qc_datasource.data.get("verbose_map", {})
if verbose_map:
df.columns = [verbose_map.get(column, column) for column in columns]
result = csv.df_to_escaped_csv(
df, index=include_index, **config["CSV_EXPORT"]
)
Expand Down
3 changes: 3 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,9 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument
SQLALCHEMY_DOCS_URL = "https://docs.sqlalchemy.org/en/13/core/engines.html"
SQLALCHEMY_DISPLAY_TEXT = "SQLAlchemy docs"

# Set to False to only allow viewing own recent activity
ENABLE_BROAD_ACTIVITY_ACCESS = True

# -------------------------------------------------------------------
# * WARNING: STOP EDITING HERE *
# -------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions superset/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class SupersetErrorType(str, Enum):
DATABASE_SECURITY_ACCESS_ERROR = "DATABASE_SECURITY_ACCESS_ERROR"
QUERY_SECURITY_ACCESS_ERROR = "QUERY_SECURITY_ACCESS_ERROR"
MISSING_OWNERSHIP_ERROR = "MISSING_OWNERSHIP_ERROR"
USER_ACTIVITY_SECURITY_ACCESS_ERROR = "USER_ACTIVITY_SECURITY_ACCESS_ERROR"

# Other errors
BACKEND_TIMEOUT_ERROR = "BACKEND_TIMEOUT_ERROR"
Expand Down
15 changes: 15 additions & 0 deletions superset/security/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,21 @@ def get_rls_ids(self, table: "BaseDatasource") -> List[int]:
ids.sort() # Combinations rather than permutations
return ids

@staticmethod
def raise_for_user_activity_access(user_id: int) -> None:
user = g.user if g.user and g.user.get_id() else None
if not user or (
not current_app.config["ENABLE_BROAD_ACTIVITY_ACCESS"]
and user_id != user.id
):
raise SupersetSecurityException(
SupersetError(
error_type=SupersetErrorType.USER_ACTIVITY_SECURITY_ACCESS_ERROR,
message="Access to user's activity data is restricted",
level=ErrorLevel.ERROR,
)
)

@staticmethod
def raise_for_dashboard_access(dashboard: "Dashboard") -> None:
"""
Expand Down
16 changes: 15 additions & 1 deletion superset/sql_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ def _extract_from_token(self, token: Token) -> None:

table_name_preceding_token = False

# If the table name is a reserved word (eg, "table_name") it won't be returned. We
# fix this by ensuring that at least one identifier is returned after the FROM
# before stopping on a keyword.
has_processed_identifier = False

for item in token.tokens:
if item.is_group and (
not self._is_identifier(item) or isinstance(item.tokens[0], Parenthesis)
Expand All @@ -318,16 +323,25 @@ def _extract_from_token(self, token: Token) -> None:
table_name_preceding_token = True
continue

if item.ttype in Keyword:
# If we haven't processed any identifiers it means the table name is a
# reserved keyword (eg, "table_name") and we shouldn't skip it.
if item.ttype in Keyword and has_processed_identifier:
table_name_preceding_token = False
continue
if table_name_preceding_token:
if isinstance(item, Identifier):
self._process_tokenlist(item)
has_processed_identifier = True
elif isinstance(item, IdentifierList):
for token2 in item.get_identifiers():
if isinstance(token2, TokenList):
self._process_tokenlist(token2)
has_processed_identifier = True
elif item.ttype in Keyword:
# convert into an identifier
fixed = Identifier([Token(Name, item.value)])
self._process_tokenlist(fixed)
has_processed_identifier = True
elif isinstance(item, IdentifierList):
if any(not self._is_identifier(token2) for token2 in item.tokens):
self._extract_from_token(item)
Expand Down
18 changes: 12 additions & 6 deletions superset/translations/babel.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@

[python: superset/**.py]
[jinja2: superset/**/templates/**.html]
[javascript: superset-frontend/src/**.js?]
[javascript: superset-frontend/src/**.ts?]
[javascript: superset-frontend/packages/**/src/**.js?]
[javascript: superset-frontend/packages/**/src/**.ts?]
[javascript: superset-frontend/plugins/**/src/**.js?]
[javascript: superset-frontend/plugins/**/src/**.ts?]
[javascript: superset-frontend/src/**.js]
[javascript: superset-frontend/src/**.jsx]
[javascript: superset-frontend/src/**.ts]
[javascript: superset-frontend/src/**.tsx]
[javascript: superset-frontend/packages/**/src/**.js]
[javascript: superset-frontend/packages/**/src/**.jsx]
[javascript: superset-frontend/packages/**/src/**.ts]
[javascript: superset-frontend/packages/**/src/**.tsx]
[javascript: superset-frontend/plugins/**/src/**.js]
[javascript: superset-frontend/plugins/**/src/**.jsx]
[javascript: superset-frontend/plugins/**/src/**.ts]
[javascript: superset-frontend/plugins/**/src/**.tsx]

encoding = utf-8
Loading

0 comments on commit a23a886

Please sign in to comment.