Skip to content

Commit

Permalink
Merge pull request frappe#2110 from arunmathaisk/master
Browse files Browse the repository at this point in the history
feat: Automating app publishing steps and validations
  • Loading branch information
arunmathaisk authored Sep 11, 2024
2 parents df21da0 + 508d133 commit 242cd95
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 17 deletions.
59 changes: 58 additions & 1 deletion dashboard/src2/components/MarketplaceAppListing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,41 +61,73 @@
v-model="marketplaceApp.title"
/>
</div>
<div class="sm:col-span-4 pb-8">
<span class="text-base font-medium">App Source</span>
<p class="text-xs pt-2">
Note: Only open-source or source-available applications are
allowed on the Frappe Marketplace. You can keep your repository
private, but if a user requests the source code, you must provide
it.
</p>
<div>
<FormControl
class="pt-4"
label="GitHub Repository URL"
type="text"
:disabled="true"
v-model="marketplaceApp.github_repository_url"
/>
<p
v-if="marketplaceApp.is_public_repo"
class="text-xs text-green-700 pt-3"
>
The GitHub repository is public.
</p>
<p v-else class="text-xs text-red-700 pt-3">
The GitHub repository is private.
</p>
</div>
</div>
<div class="sm:col-span-4">
<span class="text-base font-medium">Links</span>
<div>
<FormControl
class="mt-4"
label="Documentation"
type="text"
@blur="validateLink('documentation')"
@input="editing = true"
v-model="marketplaceApp.documentation"
/>
<FormControl
class="mt-4"
label="Website"
type="text"
@blur="validateLink('website')"
@input="editing = true"
v-model="marketplaceApp.website"
/>
<FormControl
class="mt-4"
label="Support"
type="text"
@blur="validateLink('support')"
@input="editing = true"
v-model="marketplaceApp.support"
/>
<FormControl
class="mt-4"
label="Terms of Service"
type="text"
@blur="validateLink('terms_of_service')"
@input="editing = true"
v-model="marketplaceApp.terms_of_service"
/>
<FormControl
class="mt-4"
label="Privacy Policy"
type="text"
@blur="validateLink('privacy_policy')"
@input="editing = true"
v-model="marketplaceApp.privacy_policy"
/>
Expand Down Expand Up @@ -199,7 +231,9 @@ export default {
terms_of_service: '',
privacy_policy: '',
description: '',
long_description: ''
long_description: '',
github_repository_url: '',
is_public_repo: false
}
};
},
Expand Down Expand Up @@ -230,6 +264,9 @@ export default {
auto: true,
onSuccess(response) {
this.marketplaceApp = { ...this.marketplaceApp, ...response.message };
this.setShowIsPublicGithubRepository(
this.marketplaceApp.github_repository_url
);
},
onError(e) {
toast.error(
Expand Down Expand Up @@ -295,6 +332,26 @@ export default {
}
}
];
},
validateLink(link) {
const value = this.marketplaceApp[link];
// Regular expression to validate URL format
const urlPattern =
/^(https?:\/\/)?([\w\-]+\.)+[\w\-]{2,}(\/[\w\-._~:\/?#[\]@!$&'()*+,;=]*)?$/;
// Check if the link is empty
if (!value.trim()) {
this.$toast.error(`${link.replace('_', ' ')} link is empty`);
return false;
}
// Check if the link contains a valid URL
if (!urlPattern.test(value.trim())) {
this.$toast.error(`${link.replace('_', ' ')} contains an invalid URL`);
return false;
}
return true;
}
},
computed: {
Expand Down
49 changes: 37 additions & 12 deletions press/api/marketplace.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def get_install_app_options(marketplace_app: str) -> Dict:
"""Get options for installing a marketplace app"""

restricted_site_plan_release_group = frappe.get_all(
"Site Plan Release Group", fields=["parent", "release_group"], ignore_permissions=True
"Site Plan Release Group",
fields=["parent", "release_group"],
ignore_permissions=True,
)
restricted_site_plans = [x.parent for x in restricted_site_plan_release_group]
restricted_release_groups = [
Expand Down Expand Up @@ -124,7 +126,11 @@ def get_install_app_options(marketplace_app: str) -> Dict:
for group in private_groups:
benches = frappe.db.get_all(
"Bench",
filters={"team": get_current_team(), "status": "Active", "group": group.name},
filters={
"team": get_current_team(),
"status": "Active",
"group": group.name,
},
fields=["name", "cluster"],
order_by="creation desc",
limit=1,
Expand Down Expand Up @@ -876,7 +882,10 @@ def get_marketplace_subscriptions_for_site(site: str):
subscription.app_title = marketplace_app_info.title
subscription.app_image = marketplace_app_info.image
subscription.plan_info = frappe.db.get_value(
"Marketplace App Plan", subscription.plan, ["price_usd", "price_inr"], as_dict=True
"Marketplace App Plan",
subscription.plan,
["price_usd", "price_inr"],
as_dict=True,
)
subscription.is_free = frappe.db.get_value(
"Marketplace App Plan", subscription.marketplace_app_plan, "is_free"
Expand Down Expand Up @@ -907,7 +916,9 @@ def get_apps_with_plans(apps, release_group: str):

# Make sure it is a marketplace app
m_apps = frappe.db.get_all(
"Marketplace App", filters={"app": ("in", apps)}, fields=["name", "title", "image"]
"Marketplace App",
filters={"app": ("in", apps)},
fields=["name", "title", "image"],
)

frappe_version = frappe.db.get_value("Release Group", release_group, "version")
Expand Down Expand Up @@ -1325,20 +1336,34 @@ def review_steps(name):
{"step": "Add a logo for your app", "completed": True if app.image else False},
{
"step": "Add links",
"completed": True if app.website and app.support and app.documentation else False,
"completed": (
True
if app.website
and app.support
and app.documentation
and app.terms_of_service
and app.privacy_policy
else False
),
},
{
"step": "Update description and long description",
"completed": True if app.description else False,
"completed": (
True
if app.description.strip() and app.long_description.strip() != "<p></p>"
else False
),
},
{
"step": "Publish a release for version",
"completed": True
if (
frappe.db.exists("App Release Approval Request", {"marketplace_app": name})
or frappe.db.exists("App Release", {"app": name, "status": "Approved"})
)
else False,
"completed": (
True
if (
frappe.db.exists("App Release Approval Request", {"marketplace_app": name})
or frappe.db.exists("App Release", {"app": name, "status": "Approved"})
)
else False
),
},
]

Expand Down
53 changes: 49 additions & 4 deletions press/press/doctype/marketplace_app/marketplace_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)
from press.press.doctype.marketplace_app.utils import get_rating_percentage_distribution
from press.utils import get_current_team, get_last_doc
import re


class MarketplaceApp(WebsiteGenerator):
Expand Down Expand Up @@ -346,14 +347,21 @@ def get_context(self, context):
continue

frappe_source_name = frappe.get_doc(
"Release Group App", {"app": "frappe", "parent": unique_public_rgs[source.version]}
"Release Group App",
{"app": "frappe", "parent": unique_public_rgs[source.version]},
).source
frappe_source = frappe.db.get_value(
"App Source", frappe_source_name, ["repository_url", "branch"], as_dict=True
"App Source",
frappe_source_name,
["repository_url", "branch"],
as_dict=True,
)

app_source = frappe.db.get_value(
"App Source", source.source, ["repository_url", "branch", "public"], as_dict=True
"App Source",
source.source,
["repository_url", "branch", "public"],
as_dict=True,
)

supported_versions.append(
Expand Down Expand Up @@ -547,6 +555,9 @@ def site_installs(self):

@dashboard_whitelist()
def listing_details(self):
github_repository_url = frappe.get_value(
"App Source", {"app": self.app}, "repository_url"
)
return {
"support": self.support,
"website": self.website,
Expand All @@ -556,6 +567,8 @@ def listing_details(self):
"description": self.description,
"long_description": self.long_description,
"screenshots": [screenshot.image for screenshot in self.screenshots],
"github_repository_url": github_repository_url,
"is_public_repo": is_public_github_repository(github_repository_url),
}

@dashboard_whitelist()
Expand Down Expand Up @@ -587,7 +600,11 @@ def get_analytics(self):
"installs_active_benches": self.total_active_benches(),
"installs_last_week": frappe.db.count(
"Site Activity",
{"action": "Install App", "reason": self.app, "creation": (">=", last_week)},
{
"action": "Install App",
"reason": self.app,
"creation": (">=", last_week),
},
),
"total_payout": self.get_payout_amount(),
"paid_payout": self.get_payout_amount(status="Paid"),
Expand Down Expand Up @@ -681,3 +698,31 @@ def get_total_installs_by_app():
group_by="app",
)
return {installs["app"]: installs["count"] for installs in total_installs}


def is_public_github_repository(github_url):
# Match the GitHub URL pattern to extract owner and repository
match = re.search(r"github\.com/([^/]+)/([^/]+)", github_url)

if not match:
return False

owner, repo = match.groups()

api_url = f"https://api.github.com/repos/{owner}/{repo}"

try:
response = requests.get(api_url)

if response.status_code != 200:
return False

data = response.json()

# Check if the repository is public
if data.get("private") is False:
return True
else:
return False
except Exception:
return False

0 comments on commit 242cd95

Please sign in to comment.