Browse Source

Merge pull request #2266 from nocodb/feat/non-os-compare

feat: compare with non-os products
pull/2278/head
navi 3 years ago committed by GitHub
parent
commit
2040a8d1d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 111
      packages/nc-gui/components/project/spreadsheet/components/FlipCard.vue
  2. 60
      packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue
  3. 14
      packages/nc-gui/store/project.js
  4. 17
      packages/nocodb-sdk/src/lib/Api.ts
  5. 25
      packages/nocodb/src/lib/meta/api/projectApis.ts
  6. 43
      scripts/sdk/swagger.json

111
packages/nc-gui/components/project/spreadsheet/components/FlipCard.vue

@ -0,0 +1,111 @@
<template>
<div
class="flip-card"
:style="{ height, width }"
@click="handleClick"
@mouseover="handleHover(true)"
@mouseleave="handleHover(false)"
>
<div class="flipper" :style="{ transform: flipped ? 'rotateY(180deg)' : '' }">
<div class="front" :style="{ 'pointer-events': flipped ? 'none' : 'auto' }">
<slot name="front" />
</div>
<div class="back" :style="{ 'pointer-events': flipped ? 'auto' : 'none' }">
<slot name="back" />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'FlipCard',
props: {
width: {
type: Number,
required: true
},
height: {
type: Number,
required: true
},
onHover: {
type: Boolean,
default: true
},
onClick: {
type: Boolean,
default: false
},
onTime: {
type: Number,
default: 0
}
},
data: () => ({
flipped: false,
hovered: false,
flipTimer: null
}),
mounted() {
if (this.onTime > 0) {
this.flipTimer = setInterval(() => {
if (!this.hovered) {
this.flipped = !this.flipped
}
}, this.onTime)
}
},
unmounted() {
if (this.flipTimer) {
clearInterval(this.flipTimer)
}
},
methods: {
handleHover(val) {
this.hovered = val
if (this.onHover) {
this.flipped = val
}
},
handleClick() {
if (this.onClick) {
this.flipped = !this.flipped
}
}
}
}
</script>
<style lang="scss" scoped>
.flip-card {
background-color: transparent;
perspective: 1000px;
}
.flipper {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.front, .back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.front {
color: black;
}
.back {
color: black;
transform: rotateY(180deg);
}
</style>

60
packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue

@ -308,23 +308,37 @@
<v-icon small class="close-icon" @click="hideMiniSponsorCard">
mdi-close-circle-outline
</v-icon>
<!-- <extras />-->
<v-divider class="mb-2" />
<extras class="pl-1" />
<v-btn
v-t="['e:hiring']"
color="primary"
outlined
class="caption d-100 my-2 "
href="https://angel.co/company/nocodb"
target="_blank"
>
🚀 We are Hiring! 🚀
</v-btn>
<!-- <sponsor-mini nav />-->
<flip-card width="100%" height="100px" :on-time="30 * 1000" :on-hover="false">
<template #front>
<extras class="pl-1 mt-1" />
<v-btn
v-t="['e:hiring']"
color="primary"
outlined
class="caption d-100 mt-4 "
href="https://angel.co/company/nocodb"
target="_blank"
>
🚀 We are Hiring! 🚀
</v-btn>
</template>
<template #back>
<span class="caption text-left body-1 textColor--text text--lighten-1">{{ supportCost }}</span>
<v-btn
color="primary"
class="caption d-100 my-2"
outlined
href="https://github.com/sponsors/nocodb"
target="_blank"
>
<v-icon small color="red" class="mr-2">
mdi-cards-heart
</v-icon>
{{ $t('activity.sponsorUs') }}
</v-btn>
</template>
</flip-card>
</div>
</div>
</v-container>
@ -443,10 +457,11 @@ import { copyTextToClipboard } from '~/helpers/xutils'
import SponsorMini from '~/components/SponsorMini'
import CodeSnippet from '~/components/project/spreadsheet/components/CodeSnippet'
import WebhookSlider from '~/components/project/tableTabs/webhook/WebhookSlider'
import FlipCard from '~/components/project/spreadsheet/components/FlipCard'
export default {
name: 'SpreadsheetNavDrawer',
components: { WebhookSlider, CodeSnippet, SponsorMini, Extras, CreateViewDialog, draggable },
components: { WebhookSlider, CodeSnippet, SponsorMini, Extras, CreateViewDialog, draggable, FlipCard },
props: {
extraViewParams: Object,
showAdvanceOptions: Boolean,
@ -570,6 +585,13 @@ export default {
viewType = 'view'
}
return `${this.dashboardUrl}#/nc/${viewType}/${this.shareLink.uuid}`
},
supportCost() {
const cost = parseInt(this.$store.getters['project/GtrProjectCost'])
if (cost > 0) {
return `This costs ~$${cost}/year in non-open source products.`
}
return 'Your donations will help us to make this product better.'
}
},
watch: {
@ -953,8 +975,8 @@ export default {
.close-icon {
position: absolute;
right: 10px;
top: 10px;
right: 4px;
top: 12px;
z-index: 9;
opacity: 0;
transition: 0.4s opacity;

14
packages/nc-gui/store/project.js

@ -102,7 +102,11 @@ export const mutations = {
},
MutAppInfo(state, appInfo) {
state.appInfo = appInfo
}
},
MutProjectCost(state, cost) {
state.project.cost = cost
},
}
function getSerializedEnvObj(data) {
@ -293,6 +297,9 @@ export const getters = {
&& state.unserializedList[0].projectJson
&& state.unserializedList[0].projectJson.envs ? Object.keys(state.unserializedList[0].projectJson.envs) : []
},
GtrProjectCost(state) {
return state.project.cost || 0
},
}
@ -351,6 +358,11 @@ export const actions = {
this.$ncApis.clear()
this.$ncApis.setProjectId(projectId)
}
this.$api.project.cost(projectId).then(res => {
if (res.cost) commit('MutProjectCost', res.cost)
})
} catch (e) {
console.log(e)
this.$toast.error(e).goAway(3000)

17
packages/nocodb-sdk/src/lib/Api.ts

@ -1311,6 +1311,23 @@ export class Api<
...params,
}),
/**
* @description Project compare cost
*
* @tags Project
* @name Cost
* @summary Project compare cost
* @request GET:/api/v1/db/meta/projects/{projectId}/cost
* @response `200` `object` OK
*/
cost: (projectId: string, params: RequestParams = {}) =>
this.request<object, any>({
path: `/api/v1/db/meta/projects/${projectId}/cost`,
method: 'GET',
format: 'json',
...params,
}),
/**
* No description
*

25
packages/nocodb/src/lib/meta/api/projectApis.ts

@ -390,6 +390,26 @@ export async function projectInfoGet(req, res) {
});
}
export async function projectCost(req, res) {
let cost = 0
const project = await Project.getWithInfo(req.params.projectId);
const sqlClient = NcConnectionMgrv2.getSqlClient(project.bases[0]);
const userCount = await ProjectUser.getUsersCount(req.query)
const recordCount = (await sqlClient.totalRecords())?.data.TotalRecords
if (recordCount > 100000) { // 36,000 or $79/user/month
cost = Math.max(36000, 948 * userCount)
} else if (recordCount > 50000) { // $36,000 or $50/user/month
cost = Math.max(36000, 600 * userCount)
} else if (recordCount > 10000) { // $240/user/yr
cost = Math.min(240 * userCount, 36000)
} else if (recordCount > 1000) { // $120/user/yr
cost = Math.min(120 * userCount, 36000)
}
res.json({ cost });
}
export default router => {
router.get(
'/api/v1/db/meta/projects/:projectId/info',
@ -401,6 +421,11 @@ export default router => {
metaApiMetrics,
ncMetaAclMw(projectGet, 'projectGet')
);
router.get(
'/api/v1/db/meta/projects/:projectId/cost',
metaApiMetrics,
ncMetaAclMw(projectCost, 'projectCost')
);
router.delete(
'/api/v1/db/meta/projects/:projectId',
metaApiMetrics,

43
scripts/sdk/swagger.json

@ -992,6 +992,49 @@
"description": ""
}
},
"/api/v1/db/meta/projects/{projectId}/cost": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "projectId",
"in": "path",
"required": true
}
],
"get": {
"summary": "Project compare cost",
"operationId": "project-cost",
"description": "Project compare cost",
"parameters": [
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-auth",
"description": "Auth token"
}
],
"tags": [
"Project"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
}
}
}
}
},
"/api/v1/db/meta/projects/{projectId}/tables": {
"parameters": [
{

Loading…
Cancel
Save