mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'preview' of github.com:makeplane/plane into develop
This commit is contained in:
commit
cb2a7d0930
@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@v38
|
uses: tj-actions/changed-files@v41
|
||||||
with:
|
with:
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
apiserver:
|
apiserver:
|
||||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -2,10 +2,10 @@ name: "CodeQL"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ 'develop', 'hot-fix', 'stage-release' ]
|
branches: [ 'develop', 'preview', 'master' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ 'develop' ]
|
branches: [ 'develop', 'preview', 'master' ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '53 19 * * 5'
|
- cron: '53 19 * * 5'
|
||||||
|
|
||||||
|
@ -104,15 +104,14 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, ObjectDoesNotExist):
|
if isinstance(e, ObjectDoesNotExist):
|
||||||
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"{model_name} does not exist."},
|
{"error": f"The required object does not exist."},
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"key {e} does not exist"},
|
{"error": f" The required key does not exist."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,16 +113,15 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, ObjectDoesNotExist):
|
if isinstance(e, ObjectDoesNotExist):
|
||||||
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"{model_name} does not exist."},
|
{"error": f"The required object does not exist."},
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"key {e} does not exist"},
|
{"error": f"The required key does not exist."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -216,14 +215,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, ObjectDoesNotExist):
|
if isinstance(e, ObjectDoesNotExist):
|
||||||
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"{model_name} does not exist."},
|
{"error": f"The required object does not exist."},
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": f"The required key does not exist."}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
print(e)
|
print(e)
|
||||||
|
@ -35,14 +35,13 @@ from plane.app.serializers import (
|
|||||||
ModuleSerializer,
|
ModuleSerializer,
|
||||||
)
|
)
|
||||||
from plane.utils.integrations.github import get_github_repo_details
|
from plane.utils.integrations.github import get_github_repo_details
|
||||||
from plane.utils.importers.jira import jira_project_issue_summary
|
from plane.utils.importers.jira import jira_project_issue_summary, is_allowed_hostname
|
||||||
from plane.bgtasks.importer_task import service_importer
|
from plane.bgtasks.importer_task import service_importer
|
||||||
from plane.utils.html_processor import strip_tags
|
from plane.utils.html_processor import strip_tags
|
||||||
from plane.app.permissions import WorkSpaceAdminPermission
|
from plane.app.permissions import WorkSpaceAdminPermission
|
||||||
|
|
||||||
|
|
||||||
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
|
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
|
||||||
|
|
||||||
def get(self, request, slug, service):
|
def get(self, request, slug, service):
|
||||||
if service == "github":
|
if service == "github":
|
||||||
owner = request.GET.get("owner", False)
|
owner = request.GET.get("owner", False)
|
||||||
@ -122,6 +121,7 @@ class ImportServiceEndpoint(BaseAPIView):
|
|||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkSpaceAdminPermission,
|
WorkSpaceAdminPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def post(self, request, slug, service):
|
def post(self, request, slug, service):
|
||||||
project_id = request.data.get("project_id", False)
|
project_id = request.data.get("project_id", False)
|
||||||
|
|
||||||
@ -174,6 +174,21 @@ class ImportServiceEndpoint(BaseAPIView):
|
|||||||
data = request.data.get("data", False)
|
data = request.data.get("data", False)
|
||||||
metadata = request.data.get("metadata", False)
|
metadata = request.data.get("metadata", False)
|
||||||
config = request.data.get("config", False)
|
config = request.data.get("config", False)
|
||||||
|
|
||||||
|
cloud_hostname = metadata.get("cloud_hostname", False)
|
||||||
|
|
||||||
|
if not cloud_hostname:
|
||||||
|
return Response(
|
||||||
|
{"error": "Cloud hostname is required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_allowed_hostname(cloud_hostname):
|
||||||
|
return Response(
|
||||||
|
{"error": "Hostname is not a valid hostname."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
if not data or not metadata:
|
if not data or not metadata:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Data, config and metadata are required"},
|
{"error": "Data, config and metadata are required"},
|
||||||
@ -221,9 +236,7 @@ class ImportServiceEndpoint(BaseAPIView):
|
|||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def delete(self, request, slug, service, pk):
|
def delete(self, request, slug, service, pk):
|
||||||
importer = Importer.objects.get(
|
importer = Importer.objects.get(pk=pk, service=service, workspace__slug=slug)
|
||||||
pk=pk, service=service, workspace__slug=slug
|
|
||||||
)
|
|
||||||
|
|
||||||
if importer.imported_data is not None:
|
if importer.imported_data is not None:
|
||||||
# Delete all imported Issues
|
# Delete all imported Issues
|
||||||
@ -241,9 +254,7 @@ class ImportServiceEndpoint(BaseAPIView):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def patch(self, request, slug, service, pk):
|
def patch(self, request, slug, service, pk):
|
||||||
importer = Importer.objects.get(
|
importer = Importer.objects.get(pk=pk, service=service, workspace__slug=slug)
|
||||||
pk=pk, service=service, workspace__slug=slug
|
|
||||||
)
|
|
||||||
serializer = ImporterSerializer(importer, data=request.data, partial=True)
|
serializer = ImporterSerializer(importer, data=request.data, partial=True)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
@ -479,9 +490,7 @@ class BulkImportModulesEndpoint(BaseAPIView):
|
|||||||
[
|
[
|
||||||
ModuleLink(
|
ModuleLink(
|
||||||
module=module,
|
module=module,
|
||||||
url=module_data.get("link", {}).get(
|
url=module_data.get("link", {}).get("url", "https://plane.so"),
|
||||||
"url", "https://plane.so"
|
|
||||||
),
|
|
||||||
title=module_data.get("link", {}).get(
|
title=module_data.get("link", {}).get(
|
||||||
"title", "Original Issue"
|
"title", "Original Issue"
|
||||||
),
|
),
|
||||||
|
@ -85,14 +85,14 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|||||||
if isinstance(e, ObjectDoesNotExist):
|
if isinstance(e, ObjectDoesNotExist):
|
||||||
model_name = str(exc).split(" matching query does not exist.")[0]
|
model_name = str(exc).split(" matching query does not exist.")[0]
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"{model_name} does not exist."},
|
{"error": f"The required object does not exist."},
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"key {e} does not exist"},
|
{"error": "The required key does not exist."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -172,14 +172,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, ObjectDoesNotExist):
|
if isinstance(e, ObjectDoesNotExist):
|
||||||
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": f"{model_name} does not exist."},
|
{"error": f"The required object does not exist."},
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "The required key does not exist."}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
print(e)
|
print(e)
|
||||||
|
@ -2,13 +2,31 @@ import requests
|
|||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
def is_allowed_hostname(hostname):
|
||||||
|
allowed_lists = ["atl-paas.net", "atlassian.com", "atlassian.net", "jira.com"]
|
||||||
|
# Extract the base domain from the hostname
|
||||||
|
parsed_uri = urlparse(f"https://{hostname}") # Add scheme for urlparse to work properly
|
||||||
|
domain = parsed_uri.netloc.split(":")[0] # Removes port number if included
|
||||||
|
base_domain = ".".join(domain.split(".")[-2:]) # Extract base domain
|
||||||
|
|
||||||
|
# Check if the base domain is in the allowed list
|
||||||
|
return base_domain in allowed_lists
|
||||||
|
|
||||||
|
|
||||||
def jira_project_issue_summary(email, api_token, project_key, hostname):
|
def jira_project_issue_summary(email, api_token, project_key, hostname):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
||||||
|
if not is_allowed_hostname(hostname):
|
||||||
|
print("Errored Hostname")
|
||||||
|
return {"error": "Invalid or unauthorized hostname"}
|
||||||
|
|
||||||
auth = HTTPBasicAuth(email, api_token)
|
auth = HTTPBasicAuth(email, api_token)
|
||||||
headers = {"Accept": "application/json"}
|
headers = {"Accept": "application/json"}
|
||||||
|
|
||||||
issue_url = f"https://{hostname}/rest/api/3/search?jql=project={project_key} AND issuetype=Story"
|
issue_url = f"https://{hostname}/rest/api/3/search?jql=project={project_key} AND issuetype!=Epic"
|
||||||
issue_response = requests.request(
|
issue_response = requests.request(
|
||||||
"GET", issue_url, headers=headers, auth=auth
|
"GET", issue_url, headers=headers, auth=auth
|
||||||
).json()["total"]
|
).json()["total"]
|
||||||
@ -18,7 +36,7 @@ def jira_project_issue_summary(email, api_token, project_key, hostname):
|
|||||||
"GET", module_url, headers=headers, auth=auth
|
"GET", module_url, headers=headers, auth=auth
|
||||||
).json()["total"]
|
).json()["total"]
|
||||||
|
|
||||||
status_url = f"https://{hostname}/rest/api/3/status/?jql=project={project_key}"
|
status_url = f"https://{hostname}/rest/api/3/project/${project_key}/statuses"
|
||||||
status_response = requests.request(
|
status_response = requests.request(
|
||||||
"GET", status_url, headers=headers, auth=auth
|
"GET", status_url, headers=headers, auth=auth
|
||||||
).json()
|
).json()
|
||||||
|
@ -12,8 +12,8 @@ def search_issues(query, queryset):
|
|||||||
fields = ["name", "sequence_id"]
|
fields = ["name", "sequence_id"]
|
||||||
q = Q()
|
q = Q()
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field == "sequence_id":
|
if field == "sequence_id" and len(query) <= 20:
|
||||||
sequences = re.findall(r"\d+\.\d+|\d+", query)
|
sequences = re.findall(r"[A-Za-z0-9]{1,12}-\d+", query)
|
||||||
for sequence_id in sequences:
|
for sequence_id in sequences:
|
||||||
q |= Q(**{"sequence_id": sequence_id})
|
q |= Q(**{"sequence_id": sequence_id})
|
||||||
else:
|
else:
|
||||||
|
@ -188,7 +188,7 @@ class BasePaginator:
|
|||||||
try:
|
try:
|
||||||
cursor_result = paginator.get_result(limit=per_page, cursor=input_cursor)
|
cursor_result = paginator.get_result(limit=per_page, cursor=input_cursor)
|
||||||
except BadPaginationError as e:
|
except BadPaginationError as e:
|
||||||
raise ParseError(detail=str(e))
|
raise ParseError(detail="Error in parsing")
|
||||||
|
|
||||||
# Serialize result according to the on_result function
|
# Serialize result according to the on_result function
|
||||||
if on_results:
|
if on_results:
|
||||||
|
@ -140,7 +140,7 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
|
|||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isUserAdmin) return;
|
if (!isUserAdmin) return;
|
||||||
handleRemoveIntegration;
|
handleRemoveIntegration();
|
||||||
}}
|
}}
|
||||||
disabled={!isUserAdmin}
|
disabled={!isUserAdmin}
|
||||||
loading={deletingIntegration}
|
loading={deletingIntegration}
|
||||||
|
@ -86,7 +86,11 @@ export class ProjectService extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getGithubRepositories(url: string): Promise<GithubRepositoriesResponse> {
|
async getGithubRepositories(url: string): Promise<GithubRepositoriesResponse> {
|
||||||
return this.request(url)
|
return this.request({
|
||||||
|
method: "get",
|
||||||
|
url,
|
||||||
|
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||||
|
})
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
Loading…
Reference in New Issue
Block a user