mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
576 lines
22 KiB
Python
576 lines
22 KiB
Python
from sentry.tasks.integrations.github.language_parsers import JavascriptParser, PythonParser
|
|
from sentry.testutils.cases import TestCase
|
|
|
|
|
|
class PythonParserTestCase(TestCase):
|
|
def test_python(self):
|
|
# from https://github.com/getsentry/sentry/pull/61981
|
|
patch = """@@ -36,6 +36,7 @@\n from sentry.templatetags.sentry_helpers import small_count\n from sentry.types.referrer_ids import GITHUB_OPEN_PR_BOT_REFERRER\n from sentry.utils import metrics\n+from sentry.utils.json import JSONData\n from sentry.utils.snuba import raw_snql_query\n \n logger = logging.getLogger(__name__)\n@@ -134,10 +135,10 @@ def get_issue_table_contents(issue_list: List[Dict[str, int]]) -> List[PullReque\n # TODO(cathy): Change the client typing to allow for multiple SCM Integrations\n def safe_for_comment(\n gh_client: GitHubAppsClient, repository: Repository, pull_request: PullRequest\n-) -> bool:\n+) -> Tuple[bool, JSONData]:\n logger.info("github.open_pr_comment.check_safe_for_comment")\n try:\n- pullrequest_resp = gh_client.get_pullrequest(\n+ pr_files = gh_client.get_pullrequest_files(\n repo=repository.name, pull_number=pull_request.key\n )\n except ApiError as e:\n@@ -158,34 +159,47 @@ def safe_for_comment(\n tags={"type": GithubAPIErrorType.UNKNOWN.value, "code": e.code},\n )\n logger.exception("github.open_pr_comment.unknown_api_error", extra={"error": str(e)})\n- return False\n+ return False, []\n \n safe_to_comment = True\n- if pullrequest_resp["state"] != "open":\n- metrics.incr(\n- OPEN_PR_METRICS_BASE.format(key="rejected_comment"), tags={"reason": "incorrect_state"}\n- )\n- safe_to_comment = False\n- if pullrequest_resp["changed_files"] > OPEN_PR_MAX_FILES_CHANGED:\n+\n+ changed_file_count = 0\n+ changed_lines_count = 0\n+\n+ for file in pr_files:\n+ filename = file["filename"]\n+ # don't count the file if it was added or is not a Python file\n+ if file["status"] == "added" or not filename.endswith(".py"):\n+ continue\n+\n+ changed_file_count += 1\n+ changed_lines_count += file["changes"]\n+\n+ if changed_file_count > OPEN_PR_MAX_FILES_CHANGED:\n metrics.incr(\n OPEN_PR_METRICS_BASE.format(key="rejected_comment"), tags={"reason": "too_many_files"}\n )\n safe_to_comment = False\n- if pullrequest_resp["additions"] + pullrequest_resp["deletions"] > OPEN_PR_MAX_LINES_CHANGED:\n+ if changed_lines_count > OPEN_PR_MAX_LINES_CHANGED:\n metrics.incr(\n OPEN_PR_METRICS_BASE.format(key="rejected_comment"), tags={"reason": "too_many_lines"}\n )\n safe_to_comment = False\n- return safe_to_comment\n \n+ if not safe_to_comment:\n+ pr_files = []\n+\n+ return safe_to_comment, pr_files\n \n-def get_pr_filenames(\n- gh_client: GitHubAppsClient, repository: Repository, pull_request: PullRequest\n-) -> List[str]:\n- pr_files = gh_client.get_pullrequest_files(repo=repository.name, pull_number=pull_request.key)\n \n+def get_pr_filenames(pr_files: JSONData) -> List[str]:\n # new files will not have sentry issues associated with them\n- pr_filenames: List[str] = [file["filename"] for file in pr_files if file["status"] != "added"]\n+ # only fetch Python files\n+ pr_filenames: List[str] = [\n+ file["filename"]\n+ for file in pr_files\n+ if file["status"] != "added" and file["filename"].endswith(".py")\n+ ]\n \n logger.info("github.open_pr_comment.pr_filenames", extra={"count": len(pr_filenames)})\n return pr_filenames\n@@ -316,15 +330,22 @@ def open_pr_comment_workflow(pr_id: int) -> None:\n client = installation.get_client()\n \n # CREATING THE COMMENT\n- if not safe_for_comment(gh_client=client, repository=repo, pull_request=pull_request):\n+ logger.info("github.open_pr_comment.check_safe_for_comment")\n+\n+ # fetch the files in the PR and determine if it is safe to comment\n+ safe_to_comment, pr_files = safe_for_comment(\n+ gh_client=client, repository=repo, pull_request=pull_request\n+ )\n+\n+ if not safe_to_comment:\n logger.info("github.open_pr_comment.not_safe_for_comment")\n metrics.incr(\n OPEN_PR_METRICS_BASE.format(key="error"),\n tags={"type": "unsafe_for_comment"},\n )\n return\n \n- pr_filenames = get_pr_filenames(gh_client=client, repository=repo, pull_request=pull_request)\n+ pr_filenames = get_pr_filenames(pr_files)\n \n issue_table_contents = {}\n top_issues_per_file = []"""
|
|
assert PythonParser.extract_functions_from_patch(patch) == {
|
|
"get_issue_table_contents",
|
|
"safe_for_comment",
|
|
"open_pr_comment_workflow",
|
|
}
|
|
|
|
def test_python_in_class(self):
|
|
# from https://github.com/getsentry/sentry/pull/59152
|
|
patch = '@@ -274,6 +274,14 @@ def patch(self, request: Request, organization, member):\n \n result = serializer.validated_data\n \n+ if getattr(member.flags, "partnership:restricted"):\n+ return Response(\n+ {\n+ "detail": "This member is managed by an active partnership and cannot be modified until the end of the partnership."\n+ },\n+ status=403,\n+ )\n+\n for operation in result["operations"]:\n # we only support setting active to False which deletes the orgmember\n if self._should_delete_member(operation):\n@@ -310,6 +318,14 @@ def delete(self, request: Request, organization, member) -> Response:\n """\n Delete an organization member with a SCIM User DELETE Request.\n """\n+ if getattr(member.flags, "partnership:restricted"):\n+ return Response(\n+ {\n+ "detail": "This member is managed by an active partnership and cannot be modified until the end of the partnership."\n+ },\n+ status=403,\n+ )\n+\n self._delete_member(request, organization, member)\n metrics.incr("sentry.scim.member.delete", tags={"organization": organization})\n return Response(status=204)\n@@ -348,6 +364,14 @@ def put(self, request: Request, organization, member):\n )\n return Response(context, status=200)\n \n+ if getattr(member.flags, "partnership:restricted"):\n+ return Response(\n+ {\n+ "detail": "This member is managed by an active partnership and cannot be modified until the end of the partnership."\n+ },\n+ status=403,\n+ )\n+\n if request.data.get("sentryOrgRole"):\n # Don\'t update if the org role is the same\n if ('
|
|
assert PythonParser.extract_functions_from_patch(patch) == {
|
|
"patch",
|
|
"delete",
|
|
"put",
|
|
}
|
|
|
|
|
|
class JavascriptParserTestCase(TestCase):
|
|
def test_javascript_simple(self):
|
|
patch = """\
|
|
@@44,38@@ function hello(argument1, argument2)
|
|
|
|
@@44,38@@ export default function world({argument}) {
|
|
|
|
@@44,38@@ async function there()
|
|
|
|
@@44,38@@ async function wow(
|
|
|
|
@@44,38@@ export const blue = () => {
|
|
|
|
@@44,38@@ export const green = (argument) =>
|
|
|
|
@@44,38@@ export const ocean = async (argument) => {
|
|
|
|
@@44,38@@ const planet = async function(argument) {
|
|
|
|
@@44,38@@ const constructor = new Function(
|
|
|
|
@@44,38@@ const noMatch = {"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {
|
|
"hello",
|
|
"world",
|
|
"ocean",
|
|
"blue",
|
|
"planet",
|
|
"constructor",
|
|
"there",
|
|
"green",
|
|
"wow",
|
|
}
|
|
|
|
# reference: https://github.com/jquery/esprima/tree/main/test/fixtures/declaration/function
|
|
patch = """\
|
|
@@44,38@@ function noArguments1() { }
|
|
|
|
@@44,38@@ function noArguments2() {
|
|
|
|
@@44,38@@ function test1(t, t) { }
|
|
|
|
@@44,38@@ function test2(t, t) { }
|
|
|
|
@@44,38@@ (function test3(t, t) { })
|
|
|
|
@@44,38@@ function hasInnerFunction1() { function inner() { "use strict" } }
|
|
|
|
@@44,38@@ function hasInnerFunction2(a) { sayHi();
|
|
|
|
@@44,38@@ function hasInnerFunction3(a, b) { sayHi(); }
|
|
|
|
@@44,38@@ var varFunction1 = function() { sayHi() };
|
|
|
|
@@44,38@@ var varFunction2 = function() {
|
|
|
|
@@44,38@@ var varFunction3 = function eval() { };
|
|
|
|
@@44,38@@ var varFunction4 = function arguments(
|
|
|
|
@@44,38@@ var varFunction5 = function hi() { sayHi() };
|
|
|
|
@@44,38@@ function protoFunction(__proto__) { }
|
|
|
|
@@44,38@@ function test() { "use strict" + 42; }
|
|
|
|
@@44,38@@ function a(x, x) {'use strict';}
|
|
|
|
@@44,38@@ function hello() { sayHi(); }
|
|
|
|
@@44,38@@ function lol() { }
|
|
"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {
|
|
"noArguments1",
|
|
"noArguments2",
|
|
"test1",
|
|
"test2",
|
|
"test3",
|
|
"hasInnerFunction1",
|
|
"hasInnerFunction2",
|
|
"hasInnerFunction3",
|
|
"varFunction1",
|
|
"varFunction2",
|
|
"varFunction3",
|
|
"varFunction4",
|
|
"varFunction5",
|
|
"protoFunction",
|
|
"test",
|
|
"a",
|
|
"hello",
|
|
"lol",
|
|
}
|
|
|
|
# reference https://github.com/jquery/esprima/tree/main/test/fixtures/ES6/arrow-function
|
|
# tests arrow functions on the same line as the hunk header
|
|
patch = """\
|
|
@@44,38@@ export const arrow1 = (...a) => {
|
|
|
|
@@44,38@@ var arrow2 = (a, ...b) => 0;
|
|
|
|
@@44,38@@ var arrow3 = ((a)) => 0
|
|
|
|
@@44,38@@ const arrow4 = (sun) => earth
|
|
|
|
@@44,38@@ const arrow5 = foo((x, y) => {})
|
|
|
|
@@44,38@@ const arrow6 = foo(() => {})
|
|
|
|
@@44,38@@ const arrow7 = (x) => ((y, z) => (x, y, z))
|
|
|
|
@@44,38@@ const arrow8 = x => y => 42
|
|
|
|
@@44,38@@ const arrow9 = (x) => y => 42
|
|
|
|
@@44,38@@ const arrow10 = (x => x)
|
|
|
|
@@44,38@@ const arrow11 = (eval, a = 10) => 42
|
|
|
|
@@44,38@@ const arrow12 = (eval = 10) => 42
|
|
|
|
@@44,38@@ const arrow13 = (eval, a) => 42
|
|
|
|
@@44,38@@ const arrow14 = (a) => 00
|
|
|
|
@@44,38@@ const arrow15 = arguments => 42
|
|
|
|
@@44,38@@ const arrow16 = (x=1) => x * x
|
|
|
|
@@44,38@@ const arrow17 = (a, b) => { 42; }
|
|
|
|
@@44,38@@ const arrow18 = e => { label: 42 }
|
|
|
|
@@44,38@@ const arrow19 = e => ({ property: 42 })
|
|
|
|
@@44,38@@ const arrow20 = e => { 42; }
|
|
|
|
@@44,38@@ const arrow21 = (a, b) => "test"
|
|
|
|
@@44,38@@ const arrow22 = (e) => "test"
|
|
|
|
@@44,38@@ const arrow23 = e => e + 1
|
|
|
|
@@44,38@@ const arrow24 = e => "test"
|
|
|
|
@@44,38@@ const arrow25 = () => "test"
|
|
|
|
@@44,38@@ var arrow26 = (...a) => 0
|
|
|
|
@@44,38@@ var arrow27 = (...a, ...b) => 0
|
|
|
|
@@44,38@@ var arrow28 = (a,b,...c) => 0;
|
|
|
|
@@44,38@@ var arrow29 = (a ...b) => 0
|
|
"""
|
|
|
|
functions = JavascriptParser.extract_functions_from_patch(patch)
|
|
|
|
for i in range(1, 30):
|
|
assert "arrow" + str(i) in functions
|
|
|
|
def test_typescript_simple(self):
|
|
patch = """\
|
|
@@44,38@@ function hello():
|
|
|
|
@@44,38@@ export default function world({argument: int, other: string}) {
|
|
|
|
@@44,38@@ async function there()
|
|
|
|
@@44,38@@ async function wow(
|
|
|
|
@@44,38@@ export const blue = () => {
|
|
|
|
@@44,38@@ export const green = (argument: string) =>
|
|
|
|
@@44,38@@ export const ocean = useMemo(() => {
|
|
|
|
@@44,38@@ const planet = async function({argument, ...props}) {
|
|
|
|
@@44,38@@ const constructor = new Function(
|
|
|
|
@@44,38@@ const noMatch = {"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {
|
|
"hello",
|
|
"world",
|
|
"ocean",
|
|
"blue",
|
|
"planet",
|
|
"constructor",
|
|
"there",
|
|
"green",
|
|
"wow",
|
|
}
|
|
|
|
# reference: https://github.com/jquery/esprima/tree/main/test/fixtures/declaration/function
|
|
patch = """\
|
|
@@44,38@@ function noArguments1() { }
|
|
|
|
@@44,38@@ function noArguments2() {
|
|
|
|
@@44,38@@ function test1(t: string, t: int) { }
|
|
|
|
@@44,38@@ function test2(t: int[], t: string[]) { }
|
|
|
|
@@44,38@@ (function test3(t, t) { })
|
|
|
|
@@44,38@@ function hasInnerFunction1() { function inner() { "use strict" } }
|
|
|
|
@@44,38@@ function hasInnerFunction2(a: string) { sayHi();
|
|
|
|
@@44,38@@ function hasInnerFunction3(a: string, b: string) { sayHi(); }
|
|
|
|
@@44,38@@ var varFunction1 = function() { sayHi() };
|
|
|
|
@@44,38@@ var varFunction2 = function() {
|
|
|
|
@@44,38@@ var varFunction3 = function eval() { };
|
|
|
|
@@44,38@@ var varFunction4 = function arguments(
|
|
|
|
@@44,38@@ var varFunction5 = function hi() { sayHi() };
|
|
|
|
@@44,38@@ function protoFunction(__proto__) { }
|
|
|
|
@@44,38@@ function test() { "use strict" + 42; }
|
|
|
|
@@44,38@@ function a(x: string, x: int) {'use strict';}
|
|
|
|
@@44,38@@ function hello() { sayHi(); }
|
|
|
|
@@44,38@@ function lol() { }
|
|
"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {
|
|
"noArguments1",
|
|
"noArguments2",
|
|
"test1",
|
|
"test2",
|
|
"test3",
|
|
"hasInnerFunction1",
|
|
"hasInnerFunction2",
|
|
"hasInnerFunction3",
|
|
"varFunction1",
|
|
"varFunction2",
|
|
"varFunction3",
|
|
"varFunction4",
|
|
"varFunction5",
|
|
"protoFunction",
|
|
"test",
|
|
"a",
|
|
"hello",
|
|
"lol",
|
|
}
|
|
|
|
# reference https://github.com/jquery/esprima/tree/main/test/fixtures/ES6/arrow-function
|
|
# tests arrow functions on the same line as the hunk header
|
|
patch = """\
|
|
@@44,38@@ export const arrow1 = (...a) => {
|
|
|
|
@@44,38@@ var arrow2 = (a, ...b) => 0;
|
|
|
|
@@44,38@@ var arrow3 = ((a)) => 0
|
|
|
|
@@44,38@@ const arrow4 = (sun: planet) => earth
|
|
|
|
@@44,38@@ const arrow5 = foo((x: a, y: b) => {})
|
|
|
|
@@44,38@@ const arrow6 = foo(() => {})
|
|
|
|
@@44,38@@ const arrow7 = (x: string) => ((y, z) => (x, y, z))
|
|
|
|
@@44,38@@ const arrow8 = x => y => 42
|
|
|
|
@@44,38@@ const arrow9 = (x: () => {}) => y => 42
|
|
|
|
@@44,38@@ const arrow10 = (x => x)
|
|
|
|
@@44,38@@ const arrow11 = (eval: string, a: int = 10) => 42
|
|
|
|
@@44,38@@ const arrow12 = (eval:int = 10) => 42
|
|
|
|
@@44,38@@ const arrow13 = (eval: int, a: () => void) => 42
|
|
|
|
@@44,38@@ const arrow14 = (a: int) => 00
|
|
|
|
@@44,38@@ const arrow15 = arguments => 42
|
|
|
|
@@44,38@@ const arrow16 = (x: int=1) => x * x
|
|
|
|
@@44,38@@ const arrow17 = (a: int, b: int) => { 42; }
|
|
|
|
@@44,38@@ const arrow18 = e => { label: 42 }
|
|
|
|
@@44,38@@ const arrow19 = e => ({ property: 42 })
|
|
|
|
@@44,38@@ const arrow20 = e => { 42; }
|
|
|
|
@@44,38@@ const arrow21 = (a: () => void, b: int) => "test"
|
|
|
|
@@44,38@@ const arrow22 = (e: string) => "test"
|
|
|
|
@@44,38@@ const arrow23 = e => e + 1
|
|
|
|
@@44,38@@ const arrow24 = e => "test"
|
|
|
|
@@44,38@@ const arrow25 = () => "test"
|
|
|
|
@@44,38@@ var arrow26 = (...a) => 0
|
|
|
|
@@44,38@@ var arrow27 = (...a, ...b) => 0
|
|
|
|
@@44,38@@ var arrow28 = (a: string,b: int,...c) => 0;
|
|
|
|
@@44,38@@ var arrow29 = (a ...b) => 0
|
|
"""
|
|
|
|
functions = JavascriptParser.extract_functions_from_patch(patch)
|
|
|
|
for i in range(1, 30):
|
|
assert "arrow" + str(i) in functions
|
|
|
|
def test_typescript_example(self):
|
|
# from https://github.com/getsentry/sentry/pull/61329
|
|
patch = """\
|
|
@@ -40,6 +40,7 @@ import {space} from 'sentry/styles/space';
|
|
import {Organization} from 'sentry/types';
|
|
import {isDemoWalkthrough} from 'sentry/utils/demoMode';
|
|
import {getDiscoverLandingUrl} from 'sentry/utils/discover/urls';
|
|
+import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
|
|
import theme from 'sentry/utils/theme';
|
|
import {useLocation} from 'sentry/utils/useLocation';
|
|
import useMedia from 'sentry/utils/useMedia';
|
|
@@ -115,6 +116,7 @@ function Sidebar({organization}: Props) {
|
|
|
|
const collapsed = !!preferences.collapsed;
|
|
const horizontal = useMedia(`(max-width: ${theme.breakpoints.medium})`);
|
|
+ const hasSuperuserSession = isActiveSuperuser(organization);
|
|
|
|
useOpenOnboardingSidebar();
|
|
|
|
@@ -497,7 +499,11 @@ function Sidebar({organization}: Props) {
|
|
);
|
|
|
|
return (
|
|
- <SidebarWrapper aria-label={t('Primary Navigation')} collapsed={collapsed}>
|
|
+ <SidebarWrapper
|
|
+ aria-label={t('Primary Navigation')}
|
|
+ collapsed={collapsed}
|
|
+ isSuperuser={hasSuperuserSession}
|
|
+ >
|
|
<SidebarSectionGroupPrimary>
|
|
<SidebarSection>
|
|
<SidebarDropdown
|
|
@@ -634,9 +640,10 @@ const responsiveFlex = css`
|
|
}
|
|
`;
|
|
|
|
-export const SidebarWrapper = styled('nav')<{collapsed: boolean}>`
|
|
- background: ${p => p.theme.sidebarGradient};
|
|
- color: ${p => p.theme.sidebar.color};
|
|
+export const SidebarWrapper = styled('nav')<{collapsed: boolean; isSuperuser?: boolean}>`
|
|
+ background: ${p =>
|
|
+ p.isSuperuser ? p.theme.superuserSidebar : p.theme.sidebarGradient};
|
|
+ color: ${p => (p.isSuperuser ? 'white' : p.theme.sidebar.color)};
|
|
line-height: 1;
|
|
padding: 12px 0 2px; /* Allows for 32px avatars */
|
|
width: ${p => p.theme.sidebar[p.collapsed ? 'collapsedWidth' : 'expandedWidth']};"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {"Sidebar"}
|
|
|
|
# from https://github.com/getsentry/sentry/pull/55411
|
|
patch = """\
|
|
@@ -1,9 +1,11 @@
|
|
import {useCallback, useEffect, useState} from 'react';
|
|
import styled from '@emotion/styled';
|
|
+import * as qs from 'query-string';
|
|
|
|
import {openInviteMissingMembersModal} from 'sentry/actionCreators/modal';
|
|
import {promptsCheck, promptsUpdate} from 'sentry/actionCreators/prompts';
|
|
import {Button} from 'sentry/components/button';
|
|
+import ButtonBar from 'sentry/components/buttonBar';
|
|
import Card from 'sentry/components/card';
|
|
import Carousel from 'sentry/components/carousel';
|
|
import {openConfirmModal} from 'sentry/components/confirm';
|
|
@@ -16,6 +18,7 @@ import {space} from 'sentry/styles/space';
|
|
import {MissingMember, Organization, OrgRole} from 'sentry/types';
|
|
import {promptIsDismissed} from 'sentry/utils/promptIsDismissed';
|
|
import useApi from 'sentry/utils/useApi';
|
|
+import {useLocation} from 'sentry/utils/useLocation';
|
|
import withOrganization from 'sentry/utils/withOrganization';
|
|
|
|
type Props = {
|
|
@@ -46,6 +49,7 @@ export function InviteBanner({
|
|
const api = useApi();
|
|
const integrationName = missingMembers?.integration;
|
|
const promptsFeature = `${integrationName}_missing_members`;
|
|
+ const location = useLocation();
|
|
|
|
const snoozePrompt = useCallback(async () => {
|
|
setShowBanner(false);
|
|
@@ -56,6 +60,15 @@ export function InviteBanner({
|
|
});
|
|
}, [api, organization, promptsFeature]);
|
|
|
|
+ const openInviteModal = useCallback(() => {
|
|
+ openInviteMissingMembersModal({
|
|
+ allowedRoles,
|
|
+ missingMembers,
|
|
+ organization,
|
|
+ onClose: onModalClose,
|
|
+ });
|
|
+ }, [allowedRoles, missingMembers, organization, onModalClose]);
|
|
+
|
|
useEffect(() => {
|
|
if (hideBanner) {
|
|
return;
|
|
@@ -68,6 +81,14 @@ export function InviteBanner({
|
|
});
|
|
}, [api, organization, promptsFeature, hideBanner]);
|
|
|
|
+ useEffect(() => {
|
|
+ const {inviteMissingMembers} = qs.parse(location.search);
|
|
+
|
|
+ if (!hideBanner && inviteMissingMembers) {
|
|
+ openInviteModal();
|
|
+ }
|
|
+ }, [openInviteModal, location, hideBanner]);
|
|
+
|
|
if (hideBanner || !showBanner) {
|
|
return null;
|
|
}
|
|
@@ -134,9 +155,7 @@ export function InviteBanner({
|
|
<SeeMoreCard
|
|
key="see-more"
|
|
missingMembers={missingMembers}
|
|
- allowedRoles={allowedRoles}
|
|
- onModalClose={onModalClose}
|
|
- organization={organization}
|
|
+ openInviteModal={openInviteModal}
|
|
/>
|
|
);
|
|
|
|
@@ -157,19 +176,8 @@ export function InviteBanner({
|
|
/>
|
|
</Subtitle>
|
|
</CardTitleContent>
|
|
- <ButtonContainer>
|
|
- <Button
|
|
- priority="primary"
|
|
- size="xs"
|
|
- onClick={() =>
|
|
- openInviteMissingMembersModal({
|
|
- allowedRoles,
|
|
- missingMembers,
|
|
- onClose: onModalClose,
|
|
- organization,
|
|
- })
|
|
- }
|
|
- >
|
|
+ <ButtonBar gap={1}>
|
|
+ <Button priority="primary" size="xs" onClick={openInviteModal}>
|
|
{t('View All')}
|
|
</Button>
|
|
<DropdownMenu
|
|
@@ -181,7 +189,7 @@ export function InviteBanner({
|
|
'aria-label': t('Actions'),
|
|
}}
|
|
/>
|
|
- </ButtonContainer>
|
|
+ </ButtonBar>
|
|
</CardTitleContainer>
|
|
<Carousel>{cards}</Carousel>
|
|
</StyledCard>
|
|
@@ -191,18 +199,11 @@ export function InviteBanner({
|
|
export default withOrganization(InviteBanner);
|
|
|
|
type SeeMoreCardProps = {
|
|
- allowedRoles: OrgRole[];
|
|
missingMembers: {integration: string; users: MissingMember[]};
|
|
- onModalClose: () => void;
|
|
- organization: Organization;
|
|
+ openInviteModal: () => void;
|
|
};
|
|
|
|
-function SeeMoreCard({
|
|
- missingMembers,
|
|
- allowedRoles,
|
|
- onModalClose,
|
|
- organization,
|
|
-}: SeeMoreCardProps) {
|
|
+function SeeMoreCard({missingMembers, openInviteModal}: SeeMoreCardProps) {
|
|
const {users} = missingMembers;
|
|
|
|
return (
|
|
@@ -221,18 +222,7 @@ function SeeMoreCard({
|
|
})}
|
|
</Subtitle>
|
|
</MemberCardContent>
|
|
- <Button
|
|
- size="sm"
|
|
- priority="primary"
|
|
- onClick={() =>
|
|
- openInviteMissingMembersModal({
|
|
- allowedRoles,
|
|
- missingMembers,
|
|
- organization,
|
|
- onClose: onModalClose,
|
|
- })
|
|
- }
|
|
- >
|
|
+ <Button size="sm" priority="primary" onClick={openInviteModal}>
|
|
{t('View All')}
|
|
</Button>
|
|
</MemberCard>
|
|
@@ -269,17 +259,7 @@ export const Subtitle = styled('div')`
|
|
font-size: ${p => p.theme.fontSizeSmall};
|
|
font-weight: 400;
|
|
color: ${p => p.theme.gray300};
|
|
- & > *:first-child {
|
|
- margin-left: ${space(0.5)};
|
|
- display: flex;
|
|
- align-items: center;
|
|
- }
|
|
-`;
|
|
-
|
|
-const ButtonContainer = styled('div')`
|
|
- display: grid;
|
|
- grid-auto-flow: column;
|
|
- grid-column-gap: ${space(1)};
|
|
+ gap: ${space(0.5)};
|
|
`;
|
|
|
|
const MemberCard = styled(Card)`
|
|
@@ -305,9 +285,7 @@ const MemberCardContentRow = styled('div')`
|
|
align-items: center;
|
|
margin-bottom: ${space(0.25)};
|
|
font-size: ${p => p.theme.fontSizeSmall};
|
|
- & > *:first-child {
|
|
- margin-right: ${space(0.75)};
|
|
- }
|
|
+ gap: ${space(0.75)};
|
|
`;
|
|
|
|
export const StyledExternalLink = styled(ExternalLink)`"""
|
|
|
|
assert JavascriptParser.extract_functions_from_patch(patch) == {
|
|
"InviteBanner",
|
|
"SeeMoreCard",
|
|
}
|