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. 42
      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>

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

@ -308,23 +308,37 @@
<v-icon small class="close-icon" @click="hideMiniSponsorCard"> <v-icon small class="close-icon" @click="hideMiniSponsorCard">
mdi-close-circle-outline mdi-close-circle-outline
</v-icon> </v-icon>
<!-- <extras />-->
<v-divider class="mb-2" /> <v-divider class="mb-2" />
<flip-card width="100%" height="100px" :on-time="30 * 1000" :on-hover="false">
<extras class="pl-1" /> <template #front>
<extras class="pl-1 mt-1" />
<v-btn <v-btn
v-t="['e:hiring']" v-t="['e:hiring']"
color="primary" color="primary"
outlined outlined
class="caption d-100 my-2 " class="caption d-100 mt-4 "
href="https://angel.co/company/nocodb" href="https://angel.co/company/nocodb"
target="_blank" target="_blank"
> >
🚀 We are Hiring! 🚀 🚀 We are Hiring! 🚀
</v-btn> </v-btn>
</template>
<!-- <sponsor-mini nav />--> <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>
</div> </div>
</v-container> </v-container>
@ -443,10 +457,11 @@ import { copyTextToClipboard } from '~/helpers/xutils'
import SponsorMini from '~/components/SponsorMini' import SponsorMini from '~/components/SponsorMini'
import CodeSnippet from '~/components/project/spreadsheet/components/CodeSnippet' import CodeSnippet from '~/components/project/spreadsheet/components/CodeSnippet'
import WebhookSlider from '~/components/project/tableTabs/webhook/WebhookSlider' import WebhookSlider from '~/components/project/tableTabs/webhook/WebhookSlider'
import FlipCard from '~/components/project/spreadsheet/components/FlipCard'
export default { export default {
name: 'SpreadsheetNavDrawer', name: 'SpreadsheetNavDrawer',
components: { WebhookSlider, CodeSnippet, SponsorMini, Extras, CreateViewDialog, draggable }, components: { WebhookSlider, CodeSnippet, SponsorMini, Extras, CreateViewDialog, draggable, FlipCard },
props: { props: {
extraViewParams: Object, extraViewParams: Object,
showAdvanceOptions: Boolean, showAdvanceOptions: Boolean,
@ -570,6 +585,13 @@ export default {
viewType = 'view' viewType = 'view'
} }
return `${this.dashboardUrl}#/nc/${viewType}/${this.shareLink.uuid}` 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: { watch: {
@ -953,8 +975,8 @@ export default {
.close-icon { .close-icon {
position: absolute; position: absolute;
right: 10px; right: 4px;
top: 10px; top: 12px;
z-index: 9; z-index: 9;
opacity: 0; opacity: 0;
transition: 0.4s opacity; transition: 0.4s opacity;

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

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

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

@ -1311,6 +1311,23 @@ export class Api<
...params, ...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 * 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 => { export default router => {
router.get( router.get(
'/api/v1/db/meta/projects/:projectId/info', '/api/v1/db/meta/projects/:projectId/info',
@ -401,6 +421,11 @@ export default router => {
metaApiMetrics, metaApiMetrics,
ncMetaAclMw(projectGet, 'projectGet') ncMetaAclMw(projectGet, 'projectGet')
); );
router.get(
'/api/v1/db/meta/projects/:projectId/cost',
metaApiMetrics,
ncMetaAclMw(projectCost, 'projectCost')
);
router.delete( router.delete(
'/api/v1/db/meta/projects/:projectId', '/api/v1/db/meta/projects/:projectId',
metaApiMetrics, metaApiMetrics,

43
scripts/sdk/swagger.json

@ -992,6 +992,49 @@
"description": "" "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": { "/api/v1/db/meta/projects/{projectId}/tables": {
"parameters": [ "parameters": [
{ {

Loading…
Cancel
Save