Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@ SHELL := /bin/bash
.PHONY: bundled_off prod_web prod_worker prod_scheduler docs remove_running_dev_image clean

bundled: dev_image
docker-compose up
docker compose up

bundled_off:
docker-compose down
docker compose down

web: dev_image remove_running_dev_image
docker-compose -f containers/docker-compose.dev.yml run web
docker compose -f containers/docker-compose.dev.yml run web

worker: dev_image
docker-compose -f containers/docker-compose.dev.yml run worker
docker compose -f containers/docker-compose.dev.yml run worker

scheduler: dev_image
docker-compose -f containers/docker-compose.dev.yml run scheduler
docker compose -f containers/docker-compose.dev.yml run scheduler

terminal: dev_image
docker-compose -f containers/docker-compose.dev.yml run terminal
docker compose -f containers/docker-compose.dev.yml run terminal

prod_web:
docker-compose -f containers/docker-compose.prod.yml run web
docker compose -f containers/docker-compose.prod.yml run web

prod_worker:
docker-compose -f containers/docker-compose.prod.yml run worker
docker compose -f containers/docker-compose.prod.yml run worker

prod_scheduler:
docker-compose -f containers/docker-compose.prod.yml run scheduler
docker compose -f containers/docker-compose.prod.yml run scheduler

prod_image:
docker build --pull -t querybook .
Expand All @@ -41,7 +41,7 @@ docs_image:
docker build --pull -t querybook-docs . -f docs_website/Dockerfile

docs:
docker-compose -f docs_website/docker-compose.yml --project-directory=. up --build
docker compose -f docs_website/docker-compose.yml --project-directory=. up --build

install: install_pip_runtime_dependencies install_yarn_packages

Expand All @@ -58,7 +58,7 @@ remove_running_dev_image:
docker kill $(RUNNING_CONTAINERS) || true

test: test_image
docker-compose --file containers/docker-compose.test.yml up --abort-on-container-exit
docker compose --file containers/docker-compose.test.yml up --abort-on-container-exit

clean: clean_pyc clean_docker
clean_pyc:
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ Querybook is a Big Data IDE that allows you to discover, create, and share data
- 📝 Add additional **documentation** to your tables
- 🧮 Get lineage, sample queries, frequent user, search ranking based on **past query runs**

# Fork Modifications

- 🚫 Revoke review permissions from **non-admin users**
- ⏱ Disable running **schedules** for non-admin users
- ⚠️ Restrict code execution so only **admins** can run code without review

# Getting started

## Prerequisite
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useCallback, useMemo, useRef } from 'react';
import toast from 'react-hot-toast';

import { useSelector } from 'react-redux';
import { IStoreState } from 'redux/store';

import { ComponentType, ElementType } from 'const/analytics';
import { useQueryCells } from 'hooks/dataDoc/useQueryCells';
import { useMakeSelector } from 'hooks/redux/useMakeSelector';
Expand All @@ -22,14 +25,17 @@ export const DataDocRunAllButton: React.FunctionComponent<IProps> = ({
const queryCells = useQueryCells(docId);
const latestQueryExecutions = useMakeSelector(
makeLatestQueryExecutionsSelector,
queryCells.map((c) => c.id) ?? []
queryCells.map((c) => c.id) ?? [],
);
const hasQueryRunning = useMemo(
() => latestQueryExecutions.some((q) => q.status < 3),
[latestQueryExecutions]
[latestQueryExecutions],
);
const notification = useRef(true);

const user = useSelector((state: IStoreState) => state.user);
const isAdmin = user?.myUserInfo.isAdmin === true;

const onRunAll = useCallback(() => {
sendConfirm({
header: 'Run All Cells',
Expand All @@ -54,13 +60,15 @@ export const DataDocRunAllButton: React.FunctionComponent<IProps> = ({
loading: null,
success: 'DataDoc execution started!',
error: 'Failed to start the execution',
}
},
);
},
confirmText: 'Run',
});
}, [docId, hasQueryRunning, notification, queryCells]);

if (!isAdmin) return null;

return (
<IconButton
icon="PlayCircle"
Expand Down
52 changes: 29 additions & 23 deletions querybook/webapp/components/EnvironmentAppSidebar/EntitySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { Route } from 'react-router-dom';

import { IStoreState } from 'redux/store';

import { InfoMenuButton } from 'components/InfoMenuButton/InfoMenuButton';
import { QueryEngineStatusButton } from 'components/QueryEngineStatusButton/QueryEngineStatusButton';
import { QueryExecutionButton } from 'components/QueryExecutionButton/QueryExecutionButton';
Expand Down Expand Up @@ -30,8 +32,10 @@ export const EntitySidebar: React.FunctionComponent<IEntitySidebarProps> =
const environment = useSelector(currentEnvironmentSelector);
const queryMetastores = useSelector(queryMetastoresSelector);
const hasEnabledPeerReviewEngine = useSelector(
hasEnabledPeerReviewEngineSelector
hasEnabledPeerReviewEngineSelector,
);
const user = useSelector((state: IStoreState) => state.user);
const isAdmin = user?.myUserInfo?.isAdmin === true;

return (
<div className="EntitySidebar">
Expand Down Expand Up @@ -65,7 +69,7 @@ export const EntitySidebar: React.FunctionComponent<IEntitySidebarProps> =
tooltip={'Adhoc Query'}
tooltipPos="right"
active={location.pathname.startsWith(
`/${environment.name}/adhoc/`
`/${environment.name}/adhoc/`,
)}
title="Adhoc"
onClick={() =>
Expand All @@ -78,27 +82,29 @@ export const EntitySidebar: React.FunctionComponent<IEntitySidebarProps> =
}
/>
</Link>
<Link
to={`/${environment.name}/doc_schedules/`}
>
<IconButton
icon="Clock"
tooltip="Scheduled Docs"
tooltipPos="right"
active={location.pathname.startsWith(
`/${environment.name}/doc_schedules/`
)}
title="Scheds"
onClick={() =>
trackClick({
component:
ComponentType.LEFT_SIDEBAR,
element:
ElementType.SCHEDS_BUTTON,
})
}
/>
</Link>
{isAdmin && (
<Link
to={`/${environment.name}/doc_schedules/`}
>
<IconButton
icon="Clock"
tooltip="Scheduled Docs"
tooltipPos="right"
active={location.pathname.startsWith(
`/${environment.name}/doc_schedules/`,
)}
title="Scheds"
onClick={() =>
trackClick({
component:
ComponentType.LEFT_SIDEBAR,
element:
ElementType.SCHEDS_BUTTON,
})
}
/>
</Link>
)}
</>
)}
/>
Expand Down
46 changes: 25 additions & 21 deletions querybook/webapp/components/QueryRunButton/QueryRunButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { Tag } from 'ui/Tag/Tag';
import './QueryRunButton.scss';

const EXECUTE_QUERY_SHORTCUT = getShortcutSymbols(
KeyMap.codeEditor.runQuery.key
KeyMap.codeEditor.runQuery.key,
);

interface IQueryRunButtonProps extends IQueryEngineSelectorProps {
Expand Down Expand Up @@ -77,8 +77,10 @@ export const QueryRunButton = React.forwardRef<
onSampleRateChange,
onTableSamplingInfoClick,
},
ref
ref,
) => {
const user = useSelector((state: IStoreState) => state.user);
const isAdmin = user?.myUserInfo.isAdmin === true;
const runButtonRef = React.useRef<IAsyncButtonHandles>();

React.useImperativeHandle(
Expand All @@ -90,24 +92,26 @@ export const QueryRunButton = React.forwardRef<
}
},
}),
[]
[],
);

const runButtonDOM = disabled ? null : (
<AsyncButton
onClick={onRunClick}
ref={runButtonRef}
className={clsx({
'run-selection': !!hasSelection,
})}
title={hasSelection ? 'Run Selection' : null}
icon={hasSelection ? null : <Icon name="Play" fill />}
aria-label={`Run (${EXECUTE_QUERY_SHORTCUT})`}
data-balloon-length="fit"
data-balloon-pos={runButtonTooltipPos}
color={'accent'}
/>
);
const runButtonDOM =
disabled || !isAdmin ? null : (
<AsyncButton
onClick={onRunClick}
ref={runButtonRef}
disabled={disabled}
className={clsx({
'run-selection': !!hasSelection,
})}
title={hasSelection ? 'Run Selection' : null}
icon={hasSelection ? null : <Icon name="Play" fill />}
aria-label={`Run (${EXECUTE_QUERY_SHORTCUT})`}
data-balloon-length="fit"
data-balloon-pos={runButtonTooltipPos}
color={'accent'}
/>
);

const tableSamplingDOM =
!disabled && TABLE_SAMPLING_CONFIG.enabled && hasSamplingTables ? (
Expand Down Expand Up @@ -144,7 +148,7 @@ export const QueryRunButton = React.forwardRef<
{runButtonDOM}
</div>
);
}
},
);

interface IQueryEngineSelectorProps {
Expand Down Expand Up @@ -190,7 +194,7 @@ export const QueryEngineSelector: React.FC<IQueryEngineSelectorProps> = ({

const engineItems = queryEngines
.filter((engineInfo) =>
`${engineInfo.name.toLowerCase()}`.includes(keyword.toLowerCase())
`${engineInfo.name.toLowerCase()}`.includes(keyword.toLowerCase()),
)
.map((engineInfo) => ({
name: <span className="query-engine-name">{engineInfo.name}</span>,
Expand Down Expand Up @@ -300,7 +304,7 @@ const TableSamplingSelector: React.FC<{
const sampleRateOptions = React.useMemo(getTableSamplingRateOptions, []);
const userDefaultTableSampleRate = useSelector((state: IStoreState) => {
const sampleRateSetting = parseFloat(
state.user.computedSettings['table_sample_rate']
state.user.computedSettings['table_sample_rate'],
);
return isNaN(sampleRateSetting)
? TABLE_SAMPLING_CONFIG.default_sample_rate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,46 @@ function getUserName(user: IUserSearchResultRow) {
return (user.fullname || user.username || 'Unknown').trim();
}

///////////////////////////////////////////////////////

// If you want the review section to show only admin users,
// so that regular users can submit requests only to admins,
// replace this function and list the admin usernames in the array below (adminUsernames).

// const loadOptions = debounce(
// (name, callback) => {
// const adminUsernames = ['admin'];
// SearchUserResource.search({ name }).then(({ data }) => {
// console.log(data);
// const options: any[] = [];
// for (let i = 0; i < data.length; i++) {
// const user = data[i];
// if (adminUsernames.includes(user.username)) {
// options.push({
// value: user.id,
// label: (
// <UserBadge
// uid={user.id}
// name={getUserName(user)}
// mini
// />
// ),
// isUser: true,
// });
// }
// }

// callback(options);
// });
// },
// 1000,
// {
// leading: true,
// }
// );

///////////////////////////////////////////////////////

const loadOptions = debounce(
(name, callback) => {
SearchUserResource.search({ name }).then(({ data }) => {
Expand All @@ -50,14 +90,14 @@ const loadOptions = debounce(
/>
),
isUser: true,
}))
})),
);
});
},
1000,
{
leading: true,
}
},
);

interface IUserSelectProps {
Expand All @@ -75,9 +115,9 @@ export const MultiCreatableUserSelect: React.FunctionComponent<
() =>
makeReactSelectStyle(
usePortalMenu,
multiCreatableReactSelectStyles
multiCreatableReactSelectStyles,
),
[usePortalMenu]
[usePortalMenu],
);
if (usePortalMenu) {
selectProps.menuPortalTarget = overlayRoot;
Expand All @@ -95,7 +135,7 @@ export const MultiCreatableUserSelect: React.FunctionComponent<
v.value
)),
})),
[value]
[value],
);

return (
Expand Down